Merge branch 'master' of https://github.com/FreeCAD/FreeCAD into toolbit-gui

This commit is contained in:
Patrick Felixberger
2020-04-20 17:00:18 +02:00
187 changed files with 22068 additions and 14178 deletions
+1 -1
View File
@@ -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']
+1 -15
View File
@@ -10,8 +10,6 @@
%global plugins Drawing Fem FreeCAD Image Import Inspection Mesh MeshPart Part Points QtUnit Raytracing ReverseEngineering Robot Sketcher Start Web PartDesignGui _PartDesign Path PathGui Spreadsheet SpreadsheetGui area DraftUtils DraftUtils libDriver libDriverDAT libDriverSTL libDriverUNV libMEFISTO2 libSMDS libSMESH libSMESHDS libStdMeshers Measure TechDraw TechDrawGui libarea-native Surface SurfaceGui PathSimulator
# Some configuration options for other environments
# rpmbuild --with=occ: Compile using OpenCASCADE instead of OCE
%global occ %{?_with_occ: 1} %{?!_with_occ: 0}
# rpmbuild --with=bundled_zipios: use bundled version of zipios++
%global bundled_zipios %{?_with_bundled_zipios: 1} %{?!_with_bundled_zipios: 0}
# rpmbuild --without=bundled_pycxx: don't use bundled version of pycxx
@@ -50,13 +48,7 @@ BuildRequires: git
BuildRequires: Coin3-devel
BuildRequires: Inventor-devel
%if %{occ}
BuildRequires: OpenCASCADE-devel
%else
BuildRequires: OCE-devel
BuildRequires: OCE-draw
%endif
BuildRequires: opencascade-devel
BuildRequires: boost-devel
BuildRequires: boost-python3-devel
BuildRequires: eigen3-devel
@@ -79,19 +71,15 @@ BuildRequires: netgen-mesher-devel-private
BuildRequires: python3-pivy
BuildRequires: mesa-libEGL-devel
BuildRequires: pcl-devel
%if 0%{?fedora} > 29
BuildRequires: pyside2-tools
%endif
BuildRequires: python3
BuildRequires: python3-devel
BuildRequires: python3-matplotlib
%if ! %{bundled_pycxx}
BuildRequires: python3-pycxx-devel
%endif
%if 0%{?fedora} > 29
BuildRequires: python3-pyside2-devel
BuildRequires: python3-shiboken2-devel
%endif
BuildRequires: qt5-devel
BuildRequires: qt5-qtwebkit-devel
%if ! %{bundled_smesh}
@@ -210,9 +198,7 @@ LDFLAGS='-Wl,--as-needed -Wl,--no-undefined'; export LDFLAGS
-DCOIN3D_INCLUDE_DIR=%{_includedir}/Coin3 \
-DCOIN3D_DOC_PATH=%{_datadir}/Coin3/Coin \
-DFREECAD_USE_EXTERNAL_PIVY=TRUE \
%if %{occ}
-DUSE_OCC=TRUE \
%endif
%if ! %{bundled_smesh}
-DFREECAD_USE_EXTERNAL_SMESH=TRUE \
-DSMESH_FOUND=TRUE \
+2
View File
@@ -2,3 +2,5 @@
if (BUILD_SMESH AND NOT FREECAD_USE_EXTERNAL_SMESH)
add_subdirectory(salomesmesh)
endif()
add_subdirectory(lazy_loader)
+12
View File
@@ -0,0 +1,12 @@
SET(lazy_loader
lazy_loader.py
__init__.py
)
add_custom_target(lazy_loader ALL SOURCES
${lazy_loader}
)
SET_PYTHON_PREFIX_SUFFIX(lazy_loader)
fc_copy_sources(lazy_loader "${CMAKE_BINARY_DIR}/Ext/lazy_loader" ${lazy_loader})
install (FILES ${lazy_loader} DESTINATION "${CMAKE_INSTALL_PREFIX}/Ext/lazy_loader")
+13
View File
@@ -0,0 +1,13 @@
"""
LazyLoader will defer import of a module until first usage. Usage:
from lazy_loader.lazy_loader import LazyLoader
numpy = LazyLoader("numpy", globals(), "numpy")
or
whatever = LazyLoader("module", globals(), "module.whatever")
or to replicate import module as something
something = LazyLoader("module", globals(), "module")
"""
+60
View File
@@ -0,0 +1,60 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""A LazyLoader class."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import importlib
import types
class LazyLoader(types.ModuleType):
"""Lazily import a module, mainly to avoid pulling in large dependencies.
`contrib`, and `ffmpeg` are examples of modules that are large and not always
needed, and this allows them to only be loaded when they are used.
"""
# The lint error here is incorrect.
def __init__(self, local_name, parent_module_globals, name, warning=None): # pylint: disable=super-on-old-class
self._local_name = local_name
self._parent_module_globals = parent_module_globals
self._warning = warning
super(LazyLoader, self).__init__(name)
def _load(self):
"""Load the module and insert it into the parent's globals."""
# Import the target module and insert it into the parent's namespace
module = importlib.import_module(self.__name__)
self._parent_module_globals[self._local_name] = module
# Update this object's dict so that if someone keeps a reference to the
# LazyLoader, lookups are efficient (__getattr__ is only called on lookups
# that fail).
self.__dict__.update(module.__dict__)
return module
def __getattr__(self, item):
module = self._load()
return getattr(module, item)
def __dir__(self):
module = self._load()
return dir(module)
+8
View File
@@ -4337,6 +4337,14 @@ void PropertyXLinkList::setPyObject(PyObject *value)
PropertyXLinkSubList::setPyObject(value);
}
//for consistency with PropertyLinkList
const std::vector<App::DocumentObject*> PropertyXLinkList::getValues(void) const
{
std::vector<DocumentObject*> xLinks;
getLinks(xLinks);
return(xLinks);
}
//**************************************************************************
// PropertyXLinkContainer
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+3
View File
@@ -1309,6 +1309,9 @@ public:
virtual PyObject *getPyObject(void) override;
virtual void setPyObject(PyObject *) override;
//for consistency with PropertyLinkList
const std::vector<App::DocumentObject*> getValues(void) const;
};
+1 -1
View File
@@ -1226,7 +1226,7 @@ class _Window(ArchComponent.Component):
bb.enlarge(10)
step = obj.LouvreWidth.Value+obj.LouvreSpacing.Value
if step < bb.ZLength:
box = Part.makeBox(bb.XLength,bb.YLength,obj.LouvreWidth.Value)
box = Part.makeBox(bb.XLength,bb.YLength,obj.LouvreSpacing.Value)
boxes = []
for i in range(int(bb.ZLength/step)+1):
b = box.copy()
+1
View File
@@ -180,6 +180,7 @@ FreeCADGui.addWorkbench(ArchWorkbench)
import Arch_rc
from PySide.QtCore import QT_TRANSLATE_NOOP
FreeCADGui.addPreferencePage(":/ui/preferences-ifc.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export"))
FreeCADGui.addPreferencePage(":/ui/preferences-ifc-export.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export"))
FreeCADGui.addPreferencePage(":/ui/preferences-dae.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export"))
FreeCAD.__unit_test__ += ["TestArch"]
+1
View File
@@ -112,6 +112,7 @@
<file>ui/preferences-archdefaults.ui</file>
<file>ui/preferences-dae.ui</file>
<file>ui/preferences-ifc.ui</file>
<file>ui/preferences-ifc-export.ui</file>
<file>translations/Arch_af.qm</file>
<file>translations/Arch_ar.qm</file>
<file>translations/Arch_ca.qm</file>
@@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgSettingsArch</class>
<widget class="QWidget" name="Gui::Dialog::DlgSettingsArch">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>463</width>
<height>421</height>
</rect>
</property>
<property name="windowTitle">
<string>IFC-Export</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_7">
<property name="text">
<string>Show this dialog when exporting</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcShowDialog</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Export options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="Gui::PrefCheckBox" name="checkBox">
<property name="toolTip">
<string>Some IFC viewers don't like objects exported as extrusions.
Use this to force all objects to be exported as BREP geometry.</string>
</property>
<property name="text">
<string>Force export as Brep</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcExportAsBrep</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_4">
<property name="toolTip">
<string>Use triangulation options set in the DAE options page</string>
</property>
<property name="text">
<string>Use DAE triangulation options</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcUseDaeOptions</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_5">
<property name="toolTip">
<string>Curved shapes that cannot be represented as curves in IFC
are decomposed into flat facets.
If this is checked, additional calculation is done to join coplanar facets.</string>
</property>
<property name="text">
<string>Join coplanar facets when triangulating</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcJoinCoplanarFacets</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_9">
<property name="toolTip">
<string>When exporting objects without unique ID (UID), the generated UID
will be stored inside the FreeCAD object for reuse next time that object
is exported. This leads to smaller differences between file versions.</string>
</property>
<property name="text">
<string>Store IFC unique ID in FreeCAD objects</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcStoreUid</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_10">
<property name="toolTip">
<string>IFCOpenShell is a library that allows to import IFC files.
Its serializer functionality allows to give it an OCC shape and it will
produce adequate IFC geometry: NURBS, faceted, or anything else.
Note: The serializer is still an experimental feature!</string>
</property>
<property name="text">
<string>Use IfcOpenShell serializer if available</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcSerialize</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_12">
<property name="toolTip">
<string>2D objects will be exported as IfcAnnotation</string>
</property>
<property name="text">
<string>Export 2D objects as IfcAnnotations</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcExport2D</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_13">
<property name="toolTip">
<string>All FreeCAD object properties will be stored inside the exported objects,
allowing to recreate a full parametric model on reimport.</string>
</property>
<property name="text">
<string>Export full FreeCAD parametric model</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcExportFreeCADProperties</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_15">
<property name="toolTip">
<string>When possible, similar entities will be used only once in the file if possible.
This can reduce the file size a lot, but will make it less easily readable.</string>
</property>
<property name="text">
<string>Reuse similar entities</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcCompress</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_16">
<property name="toolTip">
<string>When possible, IFC objects that are extruded rectangles will be
exported as IfcRectangleProfileDef.
However, some other applications might have problems importing that entity.
If this is your case, you can disable this and then all profiles will be exported as IfcArbitraryClosedProfileDef.</string>
</property>
<property name="text">
<string>Disable IfcRectangleProfileDef</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>DisableIfcRectangleProfileDef</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_17">
<property name="toolTip">
<string>Some IFC types such as IfcWall or IfcBeam have special standard versions
like IfcWallStandardCase or IfcBeamStandardCase.
If this option is turned on, FreeCAD will automatically export such objects
as standard cases when the necessary conditions are met.</string>
</property>
<property name="text">
<string>Auto-detect and export as standard cases when applicable</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>getStandardCase</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_19">
<property name="toolTip">
<string>If no site is found in the FreeCAD document, a default one will be added.
A site is not mandatory but a common practice is to have at least one in the file.</string>
</property>
<property name="text">
<string>Add default site if one is not found in the document</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultSite</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_21">
<property name="toolTip">
<string>If no building is found in the FreeCAD document, a default one will be added.
Warning: The IFC standard asks for at least one building in each file. By turning this option off, you will produce a non-standard IFC file.
However, at FreeCAD, we believe having a building should not be mandatory, and this option is there to have a chance to demonstrate our point of view.</string>
</property>
<property name="text">
<string>Add default building if one is not found in the document (no standard)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultBuilding</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_20">
<property name="toolTip">
<string>If no building storey is found in the FreeCAD document, a default one will be added.
A building storey is not mandatory but a common practice to have at least one in the file.</string>
</property>
<property name="text">
<string>Add default building storey if one is not found in the document</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultStorey</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>IFC file units</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefComboBox" name="comboBox_3">
<property name="toolTip">
<string>The units you want your IFC file to be exported to. Note that IFC file are ALWAYS written in metric units. Imperial units are only a conversion applied on top of it. But some BIM applications will use this to choose which unit to work with when opening the file.</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcUnit</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
<item>
<property name="text">
<string>Metric</string>
</property>
</item>
<item>
<property name="text">
<string>Imperial</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
<customwidgets>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefComboBox</class>
<extends>QComboBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+12 -286
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>463</width>
<height>937</height>
<height>495</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,13 +17,22 @@
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_7">
<property name="text">
<string>Show this dialog when importing and exporting</string>
<string>Show this dialog when importing</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcShowDialog</cstring>
@@ -388,289 +397,6 @@ FreeCAD object properties</string>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Export options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="Gui::PrefCheckBox" name="checkBox">
<property name="toolTip">
<string>Some IFC viewers don't like objects exported as extrusions.
Use this to force all objects to be exported as BREP geometry.</string>
</property>
<property name="text">
<string>Force export as Brep</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcExportAsBrep</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_4">
<property name="toolTip">
<string>Use triangulation options set in the DAE options page</string>
</property>
<property name="text">
<string>Use DAE triangulation options</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcUseDaeOptions</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_5">
<property name="toolTip">
<string>Curved shapes that cannot be represented as curves in IFC
are decomposed into flat facets.
If this is checked, additional calculation is done to join coplanar facets.</string>
</property>
<property name="text">
<string>Join coplanar facets when triangulating</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcJoinCoplanarFacets</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_9">
<property name="toolTip">
<string>When exporting objects without unique ID (UID), the generated UID
will be stored inside the FreeCAD object for reuse next time that object
is exported. This leads to smaller differences between file versions.</string>
</property>
<property name="text">
<string>Store IFC unique ID in FreeCAD objects</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcStoreUid</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_10">
<property name="toolTip">
<string>IFCOpenShell is a library that allows to import IFC files.
Its serializer functionality allows to give it an OCC shape and it will
produce adequate IFC geometry: NURBS, faceted, or anything else.
Note: The serializer is still an experimental feature!</string>
</property>
<property name="text">
<string>Use IfcOpenShell serializer if available</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcSerialize</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_12">
<property name="toolTip">
<string>2D objects will be exported as IfcAnnotation</string>
</property>
<property name="text">
<string>Export 2D objects as IfcAnnotations</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcExport2D</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_13">
<property name="toolTip">
<string>All FreeCAD object properties will be stored inside the exported objects,
allowing to recreate a full parametric model on reimport.</string>
</property>
<property name="text">
<string>Export full FreeCAD parametric model</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcExportFreeCADProperties</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_15">
<property name="toolTip">
<string>When possible, similar entities will be used only once in the file if possible.
This can reduce the file size a lot, but will make it less easily readable.</string>
</property>
<property name="text">
<string>Reuse similar entities</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcCompress</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_16">
<property name="toolTip">
<string>When possible, IFC objects that are extruded rectangles will be
exported as IfcRectangleProfileDef.
However, some other applications might have problems importing that entity.
If this is your case, you can disable this and then all profiles will be exported as IfcArbitraryClosedProfileDef.</string>
</property>
<property name="text">
<string>Disable IfcRectangleProfileDef</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>DisableIfcRectangleProfileDef</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_17">
<property name="toolTip">
<string>Some IFC types such as IfcWall or IfcBeam have special standard versions
like IfcWallStandardCase or IfcBeamStandardCase.
If this option is turned on, FreeCAD will automatically export such objects
as standard cases when the necessary conditions are met.</string>
</property>
<property name="text">
<string>Auto-detect and export as standard cases when applicable</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>getStandardCase</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_19">
<property name="toolTip">
<string>If no site is found in the FreeCAD document, a default one will be added.
A site is not mandatory but a common practice is to have at least one in the file.</string>
</property>
<property name="text">
<string>Add default site if one is not found in the document</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultSite</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_21">
<property name="toolTip">
<string>If no building is found in the FreeCAD document, a default one will be added.
Warning: The IFC standard asks for at least one building in each file. By turning this option off, you will produce a non-standard IFC file.
However, at FreeCAD, we believe having a building should not be mandatory, and this option is there to have a chance to demonstrate our point of view.</string>
</property>
<property name="text">
<string>Add default building if one is not found in the document (no standard)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultBuilding</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_20">
<property name="toolTip">
<string>If no building storey is found in the FreeCAD document, a default one will be added.
A building storey is not mandatory but a common practice to have at least one in the file.</string>
</property>
<property name="text">
<string>Add default building storey if one is not found in the document</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>IfcAddDefaultStorey</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>IFC file units</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefComboBox" name="comboBox_3">
<property name="toolTip">
<string>The units you want your IFC file to be exported to. Note that IFC file are ALWAYS written in metric units. Imperial units are only a conversion applied on top of it. But some BIM applications will use this to choose which unit to work with when opening the file.</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcUnit</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
<item>
<property name="text">
<string>Metric</string>
</property>
</item>
<item>
<property name="text">
<string>Imperial</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
+222 -88
View File
@@ -69,8 +69,10 @@ translationtable = {
"Stair Flight":"StairFlight",
"Curtain Wall":"CurtainWall",
"Pipe Segment":"PipeSegment",
"Pipe Fitting":"PipeFitting"
}
"Pipe Fitting":"PipeFitting",
"VisGroup":"Group",
"Undefined":"BuildingElementProxy",
}
# the base IFC template for export
@@ -113,7 +115,7 @@ def getPreferences():
if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False):
import FreeCADGui
FreeCADGui.showPreferences("Import-Export",0)
FreeCADGui.showPreferences("Import-Export",1)
ifcunit = p.GetInt("ifcUnit",0)
f = 0.001
u = "metre"
@@ -137,8 +139,17 @@ def getPreferences():
'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",False),
'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True),
'IFC_UNIT': u,
'SCALE_FACTOR': f
'SCALE_FACTOR': f,
'GET_STANDARD': p.GetBool("getStandardType",False)
}
if hasattr(ifcopenshell,"schema_identifier"):
schema = ifcopenshell.schema_identifier
elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6):
# v0.6 onwards allows to set our own schema
schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)]
else:
schema = "IFC2X3"
preferences["SCHEMA"] = schema
return preferences
@@ -151,9 +162,6 @@ def export(exportList,filename,colors=None,preferences=None):
colors is an optional dictionary of objName:shapeColorTuple or objName:diffuseColorList elements
to be used in non-GUI mode if you want to be able to export colors."""
if preferences is None:
preferences = getPreferences()
try:
global ifcopenshell
import ifcopenshell
@@ -162,6 +170,11 @@ def export(exportList,filename,colors=None,preferences=None):
FreeCAD.Console.PrintMessage("Visit https://www.freecadweb.org/wiki/Arch_IFC to learn how to install it\n")
return
if preferences is None:
preferences = getPreferences()
# process template
version = FreeCAD.Version()
owner = FreeCAD.ActiveDocument.CreatedBy
email = ''
@@ -171,16 +184,8 @@ def export(exportList,filename,colors=None,preferences=None):
email = s[1].strip(">")
global template
template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2])
getstd = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("getStandardType",False)
if hasattr(ifcopenshell,"schema_identifier"):
schema = ifcopenshell.schema_identifier
elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6):
# v0.6 allows to set our own schema
schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)]
else:
schema = "IFC2X3"
if preferences['DEBUG']: print("Exporting an",schema,"file...")
template = template.replace("$ifcschema",schema)
if preferences['DEBUG']: print("Exporting an",preferences['SCHEMA'],"file...")
template = template.replace("$ifcschema",preferences['SCHEMA'])
template = template.replace("$owner",owner)
template = template.replace("$company",FreeCAD.ActiveDocument.Company)
template = template.replace("$email",email)
@@ -196,6 +201,9 @@ def export(exportList,filename,colors=None,preferences=None):
of.write(template)
of.close()
os.close(templatefilehandle)
# create IFC file
global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs
ifcfile = ifcopenshell.open(templatefile)
ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"])
@@ -211,13 +219,17 @@ def export(exportList,filename,colors=None,preferences=None):
if obj.Shape:
if obj.Shape.Edges and (not obj.Shape.Faces):
annotations.append(obj)
# clean objects list of unwanted types
objectslist = [obj for obj in objectslist if obj not in annotations]
objectslist = Arch.pruneIncluded(objectslist,strict=True)
objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Dimension","Material","MaterialContainer","WorkingPlaneProxy"]]
if preferences['FULL_PARAMETRIC']:
objectslist = Arch.getAllChildren(objectslist)
# create project and context
contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist)
context = contextCreator.model_view_subcontext
project = contextCreator.project
@@ -227,6 +239,8 @@ def export(exportList,filename,colors=None,preferences=None):
decl = Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian)
contextCreator.model_context.TrueNorth.DirectionRatios = (math.cos(decl+math.pi/2), math.sin(decl+math.pi/2))
# define holders for the different types we create
products = {} # { Name: IfcEntity, ... }
subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product
surfstyles = {} # { (r,g,b): IfcEntity, ... }
@@ -267,33 +281,81 @@ def export(exportList,filename,colors=None,preferences=None):
# getting generic data
name = obj.Label
if six.PY2:
name = name.encode("utf8")
description = obj.Description if hasattr(obj,"Description") else ""
if six.PY2:
description = description.encode("utf8")
# getting uid
uid = None
if hasattr(obj,"IfcData"):
if "IfcUID" in obj.IfcData.keys():
uid = str(obj.IfcData["IfcUID"])
if not uid:
uid = ifcopenshell.guid.new()
# storing the uid for further use
if preferences['STORE_UID'] and hasattr(obj,"IfcData"):
d = obj.IfcData
d["IfcUID"] = uid
obj.IfcData = d
name = getText("Name",obj)
description = getText("Description",obj)
uid = getUID(obj,preferences)
ifctype = getIfcTypeFromObj(obj)
if ifctype == "IfcGroup":
groups[obj.Name] = [o.Name for o in obj.Group]
continue
# handle assemblies (arrays, app::parts, references, etc...)
assemblyElements = []
if ifctype == "IfcArray":
if obj.ArrayType == "ortho":
clonedeltas = []
for i in range(obj.NumberX):
clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX))
for j in range(obj.NumberY):
if j > 0:
clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY))
for k in range(obj.NumberZ):
if k > 0:
clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)+(k*obj.IntervalZ))
#print("clonedeltas:",clonedeltas)
for delta in clonedeltas:
representation,placement,shapetype = getRepresentation(
ifcfile,
context,
obj.Base,
forcebrep=(getBrepFlag(obj.Base,preferences)),
colors=colors,
preferences=preferences,
forceclone=delta
)
subproduct = createProduct(
ifcfile,
obj.Base,
getIfcTypeFromObj(obj.Base),
getUID(obj.Base,preferences),
history,
getText("Name",obj.Base),
getText("Description",obj.Base),
placement,
representation,
preferences)
assemblyElements.append(subproduct)
ifctype = "IfcElementAssembly"
elif ifctype == "IfcApp::Part":
for subobj in [FreeCAD.ActiveDocument.getObject(n[:-1]) for n in obj.getSubObjects()]:
representation,placement,shapetype = getRepresentation(
ifcfile,
context,
subobj,
forcebrep=(getBrepFlag(subobj,preferences)),
colors=colors,
preferences=preferences
)
subproduct = createProduct(
ifcfile,
subobj,
getIfcTypeFromObj(subobj),
getUID(subobj,preferences),
history,
getText("Name",subobj),
getText("Description",subobj),
placement,
representation,
preferences)
assemblyElements.append(subproduct)
ifctype = "IfcElementAssembly"
# export grids
if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]:
@@ -356,61 +418,54 @@ def export(exportList,filename,colors=None,preferences=None):
if ifctype not in ArchIFCSchema.IfcProducts.keys():
ifctype = "IfcBuildingElementProxy"
# getting the "Force BREP" flag
brepflag = False
if hasattr(obj,"IfcData"):
if "FlagForceBrep" in obj.IfcData.keys():
if obj.IfcData["FlagForceBrep"] == "True":
brepflag = True
# getting the representation
representation,placement,shapetype = getRepresentation(
ifcfile,
context,
obj,
forcebrep=(brepflag or preferences['FORCE_BREP']),
forcebrep=(getBrepFlag(obj,preferences)),
colors=colors,
preferences=preferences
)
if getstd:
if preferences['GET_STANDARD']:
if isStandardCase(obj,ifctype):
ifctype += "StandardCase"
if preferences['DEBUG']: print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name)
# setting the arguments
kwargs = {
"GlobalId": uid,
"OwnerHistory": history,
"Name": name,
"Description": description,
"ObjectPlacement": placement,
"Representation": representation
}
if ifctype == "IfcSite":
kwargs.update({
"RefLatitude":dd2dms(obj.Latitude),
"RefLongitude":dd2dms(obj.Longitude),
"RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'],
"SiteAddress":buildAddress(obj,ifcfile),
"CompositionType": "ELEMENT"
})
if schema == "IFC2X3":
kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR'])
else:
kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR'])
if preferences['DEBUG']:
print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name)
# creating the product
#print(obj.Label," : ",ifctype," : ",kwargs)
product = getattr(ifcfile,"create"+ifctype)(**kwargs)
product = createProduct(
ifcfile,
obj,
ifctype,
uid,
history,
name,
description,
placement,
representation,
preferences)
products[obj.Name] = product
if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]:
spatialelements[obj.Name] = product
# gather assembly subelements
if assemblyElements:
ifcfile.createIfcRelAggregates(
ifcopenshell.guid.new(),
history,
'Assembly',
'',
products[obj.Name],
assemblyElements
)
if preferences['DEBUG']: print(" aggregating",len(assemblyElements),"object(s)")
# additions
if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]):
@@ -1115,7 +1170,7 @@ def export(exportList,filename,colors=None,preferences=None):
rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")])
break
if rgb:
psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2])
psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2],ifc4=(preferences["SCHEMA"]=="IFC4"))
isi = ifcfile.createIfcStyledItem(None,[psa],None)
isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi])
imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat)
@@ -1497,12 +1552,6 @@ def getIfcTypeFromObj(obj):
if ifctype in translationtable.keys():
ifctype = translationtable[ifctype]
if ifctype == "VisGroup":
ifctype = "Group"
if ifctype == "Undefined":
ifctype = "BuildingElementProxy"
if ifctype == "Furniture":
ifctype = "FurnishingElement"
return "Ifc" + ifctype
@@ -1542,6 +1591,7 @@ def exportIFC2X3Attributes(obj, kwargs, scale=0.001):
def exportIfcAttributes(obj, kwargs, scale=0.001):
ifctype = getIfcTypeFromObj(obj)
for property in obj.PropertiesList:
if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property):
value = obj.getPropertyByName(property)
@@ -1549,7 +1599,10 @@ def exportIfcAttributes(obj, kwargs, scale=0.001):
value = float(value)
if property in ["ElevationWithFlooring","Elevation"]:
value = value*scale # some properties must be changed to meters
kwargs.update({property: value})
if (ifctype == "IfcFurnishingElement") and (property == "PredefinedType"):
pass # IFC2x3 Furniture objects get converted to IfcFurnishingElement and have no PredefinedType anymore
else:
kwargs.update({property: value})
return kwargs
@@ -1743,9 +1796,10 @@ def getProfile(ifcfile,p):
return profile
def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None):
def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None,forceclone=False):
"""returns an IfcShapeRepresentation object or None"""
"""returns an IfcShapeRepresentation object or None. forceclone can be False (does nothing),
"store" or True (stores the object as clone base) or a Vector (creates a clone)"""
import Part
import DraftGeomUtils
@@ -1756,20 +1810,27 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
shapetype = "no shape"
tostore = False
subplacement = None
skipshape = False
# check for clones
if (not subtraction) and (not forcebrep):
if ((not subtraction) and (not forcebrep)) or forceclone:
if forceclone:
if not obj.Name in clones:
clones[obj.Name] = []
for k,v in clones.items():
if (obj.Name == k) or (obj.Name in v):
if k in sharedobjects:
# base shape already exists
repmap = sharedobjects[k]
pla = obj.getGlobalPlacement()
pos = FreeCAD.Vector(pla.Base)
if isinstance(forceclone,FreeCAD.Vector):
pos += forceclone
axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1))))
origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR'])))
origin = ifcbin.createIfcCartesianPoint(tuple(pos.multiply(preferences['SCALE_FACTOR'])))
transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3)
mapitem = ifcfile.createIfcMappedItem(repmap,transf)
shapes = [mapitem]
@@ -1783,7 +1844,11 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
if obj.isDerivedFrom("Part::Feature") and (len(obj.Shape.Solids) > 1) and hasattr(obj,"Axis") and obj.Axis:
forcebrep = True
if (not shapes) and (not forcebrep):
# specific cases that must ignore their own shape
if Draft.getType(obj) in ["Array"]:
skipshape = True
if (not shapes) and (not forcebrep) and (not skipshape):
profile = None
ev = FreeCAD.Vector()
if hasattr(obj,"Proxy"):
@@ -1858,7 +1923,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
solidType = "SweptSolid"
shapetype = "extrusion"
if not shapes:
if (not shapes) and (not skipshape):
# check if we keep a null shape (additions-only object)
@@ -2106,3 +2171,72 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation])
return productdef,placement,shapetype
def getBrepFlag(obj,preferences):
"""returns True if the object must be exported as BREP"""
brepflag = False
if preferences['FORCE_BREP']:
return True
if hasattr(obj,"IfcData"):
if "FlagForceBrep" in obj.IfcData.keys():
if obj.IfcData["FlagForceBrep"] == "True":
brepflag = True
return brepflag
def createProduct(ifcfile,obj,ifctype,uid,history,name,description,placement,representation,preferences):
"""creates a product in the given IFC file"""
kwargs = {
"GlobalId": uid,
"OwnerHistory": history,
"Name": name,
"Description": description,
"ObjectPlacement": placement,
"Representation": representation
}
if ifctype == "IfcSite":
kwargs.update({
"RefLatitude":dd2dms(obj.Latitude),
"RefLongitude":dd2dms(obj.Longitude),
"RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'],
"SiteAddress":buildAddress(obj,ifcfile),
"CompositionType": "ELEMENT"
})
if preferences['SCHEMA'] == "IFC2X3":
kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR'])
else:
kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR'])
product = getattr(ifcfile,"create"+ifctype)(**kwargs)
return product
def getUID(obj,preferences):
"""gets or creates an UUID for an object"""
uid = None
if hasattr(obj,"IfcData"):
if "IfcUID" in obj.IfcData.keys():
uid = str(obj.IfcData["IfcUID"])
if not uid:
uid = ifcopenshell.guid.new()
# storing the uid for further use
if preferences['STORE_UID'] and hasattr(obj,"IfcData"):
d = obj.IfcData
d["IfcUID"] = uid
obj.IfcData = d
return uid
def getText(field,obj):
"""Returns the value of a text property of an object"""
result = ""
if field == "Name":
field = "Label"
if hasattr(obj,field):
result = getattr(obj,field)
if six.PY2:
result = result.encode("utf8")
return result
+5 -2
View File
@@ -370,7 +370,7 @@ class recycler:
self.sstyles[key] = c
return c
def createIfcPresentationStyleAssignment(self,name,r,g,b,t=0):
def createIfcPresentationStyleAssignment(self,name,r,g,b,t=0,ifc4=False):
if name:
key = name+str((r,g,b,t))
else:
@@ -380,7 +380,10 @@ class recycler:
return self.psas[key]
else:
iss = self.createIfcSurfaceStyle(name,r,g,b,t)
c = self.ifcfile.createIfcPresentationStyleAssignment([iss])
if ifc4:
c = iss
else:
c = self.ifcfile.createIfcPresentationStyleAssignment([iss])
if self.compress:
self.psas[key] = c
return c
+14 -5
View File
@@ -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()
+84 -84
View File
@@ -62,46 +62,46 @@ PyMOD_INIT_FUNC(Cloud)
PyMOD_Return(mod);
}
Py::Object Cloud::Module::sCloudUrl(const Py::Tuple& args)
Py::Object Cloud::Module::sCloudURL(const Py::Tuple& args)
{
char *Url;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8",&Url)) // convert args: Python->C
char *URL;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8",&URL)) // convert args: Python->C
return Py::None();
if (this->Url.getStrValue() != Url) {
this->Url.setValue(Url);
if (this->URL.getStrValue() != URL) {
this->URL.setValue(URL);
}
return Py::None();
}
Py::Object Cloud::Module::sCloudAccessKey(const Py::Tuple& args)
Py::Object Cloud::Module::sCloudTokenAuth(const Py::Tuple& args)
{
char *AccessKey;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &AccessKey)) // convert args: Python->C
char *TokenAuth;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &TokenAuth)) // convert args: Python->C
return Py::None();
if (this->AccessKey.getStrValue() != AccessKey) {
this->AccessKey.setValue(AccessKey);
if (this->TokenAuth.getStrValue() != TokenAuth) {
this->TokenAuth.setValue(TokenAuth);
}
return Py::None();
}
Py::Object Cloud::Module::sCloudSecretKey(const Py::Tuple& args)
Py::Object Cloud::Module::sCloudTokenSecret(const Py::Tuple& args)
{
char *SecretKey;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &SecretKey)) // convert args: Python->C
char *TokenSecret;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &TokenSecret)) // convert args: Python->C
return Py::None();
if (this->SecretKey.getStrValue() != SecretKey) {
this->SecretKey.setValue(SecretKey);
if (this->TokenSecret.getStrValue() != TokenSecret) {
this->TokenSecret.setValue(TokenSecret);
}
return Py::None();
}
Py::Object Cloud::Module::sCloudTcpPort(const Py::Tuple& args)
Py::Object Cloud::Module::sCloudTCPPort(const Py::Tuple& args)
{
char *TcpPort;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &TcpPort)) // convert args: Python->C
char *TCPPort;
if (!PyArg_ParseTuple(args.ptr(), "et","utf-8", &TCPPort)) // convert args: Python->C
return Py::None();
if (this->TcpPort.getStrValue() != TcpPort) {
this->TcpPort.setValue(TcpPort);
if (this->TCPPort.getStrValue() != TCPPort) {
this->TCPPort.setValue(TCPPort);
}
return Py::None();
}
@@ -207,7 +207,7 @@ void Cloud::CloudWriter::createBucket()
char path[1024];
sprintf(path, "/%s/", this->Bucket);
RequestData = Cloud::ComputeDigestAmzS3v2("PUT", "application/xml", path, this->SecretKey, NULL, 0);
RequestData = Cloud::ComputeDigestAmzS3v2("PUT", "application/xml", path, this->TokenSecret, NULL, 0);
// Let's build the Header and call to curl
curl_global_init(CURL_GLOBAL_ALL);
@@ -220,22 +220,22 @@ void Cloud::CloudWriter::createBucket()
if ( curl )
{
struct curl_slist *chunk = NULL;
char Url[256];
char URL[256];
// Let's build our own header
std::string strUrl(this->Url);
eraseSubStr(strUrl,"http://");
eraseSubStr(strUrl,"https://");
std::string strURL(this->URL);
eraseSubStr(strURL,"http://");
eraseSubStr(strURL,"https://");
chunk = Cloud::BuildHeaderAmzS3v2( strUrl.c_str(), this->TcpPort, this->AccessKey, RequestData);
chunk = Cloud::BuildHeaderAmzS3v2( strURL.c_str(), this->TCPPort, this->TokenAuth, RequestData);
delete RequestData;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
// Lets build the Url for our Curl call
// Lets build the URL for our Curl call
sprintf(Url,"%s:%s/%s/", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/", this->URL,this->TCPPort,
this->Bucket);
curl_easy_setopt(curl, CURLOPT_URL, Url);
curl_easy_setopt(curl, CURLOPT_URL, URL);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
@@ -336,14 +336,14 @@ char *Cloud::MD5Sum(const char *ptr, long size)
return(output);
}
struct curl_slist *Cloud::BuildHeaderAmzS3v2(const char *Url, const char *TcpPort, const char *PublicKey, struct Cloud::AmzData *Data)
struct curl_slist *Cloud::BuildHeaderAmzS3v2(const char *URL, const char *TCPPort, const char *PublicKey, struct Cloud::AmzData *Data)
{
char header_data[1024];
struct curl_slist *chunk = NULL;
// Build the Host: entry
sprintf(header_data,"Host: %s:%s", Url, TcpPort);
sprintf(header_data,"Host: %s:%s", URL, TCPPort);
chunk = curl_slist_append(chunk, header_data);
// Build the Date entry
@@ -375,7 +375,7 @@ struct curl_slist *Cloud::BuildHeaderAmzS3v2(const char *Url, const char *TcpPor
return chunk;
}
Cloud::CloudWriter::CloudWriter(const char* Url, const char* AccessKey, const char* SecretKey, const char* TcpPort, const char* Bucket)
Cloud::CloudWriter::CloudWriter(const char* URL, const char* TokenAuth, const char* TokenSecret, const char* TCPPort, const char* Bucket)
{
struct Cloud::AmzData *RequestData;
CURL *curl;
@@ -383,15 +383,15 @@ Cloud::CloudWriter::CloudWriter(const char* Url, const char* AccessKey, const ch
std::string s;
this->Url=Url;
this->AccessKey=AccessKey;
this->SecretKey=SecretKey;
this->TcpPort=TcpPort;
this->URL=URL;
this->TokenAuth=TokenAuth;
this->TokenSecret=TokenSecret;
this->TCPPort=TCPPort;
this->Bucket=Bucket;
this->FileName="";
char path[1024];
sprintf(path,"/%s/", this->Bucket);
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->SecretKey, NULL, 0);
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->TokenSecret, NULL, 0);
// Let's build the Header and call to curl
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
@@ -403,21 +403,21 @@ Cloud::CloudWriter::CloudWriter(const char* Url, const char* AccessKey, const ch
{
// Let's build our own header
struct curl_slist *chunk = NULL;
char Url[256];
std::string strUrl(this->Url);
eraseSubStr(strUrl,"http://");
eraseSubStr(strUrl,"https://");
char URL[256];
std::string strURL(this->URL);
eraseSubStr(strURL,"http://");
eraseSubStr(strURL,"https://");
chunk = Cloud::BuildHeaderAmzS3v2( strUrl.c_str(), this->TcpPort, this->AccessKey, RequestData);
chunk = Cloud::BuildHeaderAmzS3v2( strURL.c_str(), this->TCPPort, this->TokenAuth, RequestData);
delete RequestData;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
// Lets build the Url for our Curl call
// Lets build the URL for our Curl call
sprintf(Url,"%s:%s/%s/", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/", this->URL,this->TCPPort,
this->Bucket);
curl_easy_setopt(curl, CURLOPT_URL, Url);
curl_easy_setopt(curl, CURLOPT_URL, URL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWrite_CallbackFunc_StdString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
@@ -557,7 +557,7 @@ Cloud::CloudReader::~CloudReader()
{
}
Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const char* SecretKey, const char* TcpPort, const char* Bucket)
Cloud::CloudReader::CloudReader(const char* URL, const char* TokenAuth, const char* TokenSecret, const char* TCPPort, const char* Bucket)
{
struct Cloud::AmzData *RequestData;
CURL *curl;
@@ -565,10 +565,10 @@ Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const ch
bool GetBucketContentList=true;
this->Url=Url;
this->AccessKey=AccessKey;
this->SecretKey=SecretKey;
this->TcpPort=TcpPort;
this->URL=URL;
this->TokenAuth=TokenAuth;
this->TokenSecret=TokenSecret;
this->TCPPort=TCPPort;
this->Bucket=Bucket;
char path[1024];
@@ -584,7 +584,7 @@ Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const ch
while ( GetBucketContentList )
{
std::string s;
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->SecretKey, NULL, 0);
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->TokenSecret, NULL, 0);
curl = curl_easy_init();
#ifdef ALLOW_SELF_SIGNED_CERTIFICATE
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
@@ -594,22 +594,22 @@ Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const ch
{
// Let's build our own header
struct curl_slist *chunk = NULL;
char Url[256];
std::string strUrl(this->Url);
eraseSubStr(strUrl,"http://");
eraseSubStr(strUrl,"https://");
char URL[256];
std::string strURL(this->URL);
eraseSubStr(strURL,"http://");
eraseSubStr(strURL,"https://");
chunk = Cloud::BuildHeaderAmzS3v2( strUrl.c_str(), this->TcpPort, this->AccessKey, RequestData);
chunk = Cloud::BuildHeaderAmzS3v2( strURL.c_str(), this->TCPPort, this->TokenAuth, RequestData);
delete RequestData;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
if ( strlen(NextFileName) == 0 )
sprintf(Url,"%s:%s/%s/?list-type=2", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/?list-type=2", this->URL,this->TCPPort,
this->Bucket);
else
sprintf(Url,"%s:%s/%s/?list-type=2&continuation-token=%s", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/?list-type=2&continuation-token=%s", this->URL,this->TCPPort,
this->Bucket, NextFileName);
curl_easy_setopt(curl, CURLOPT_URL, Url);
curl_easy_setopt(curl, CURLOPT_URL, URL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWrite_CallbackFunc_StdString);
@@ -669,7 +669,7 @@ void Cloud::CloudReader::DownloadFile(Cloud::CloudReader::FileEntry *entry)
// We must get the directory content
char path[1024];
sprintf(path, "/%s/%s", this->Bucket, entry->FileName);
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/octet-stream", path, this->SecretKey, NULL, 0);
RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/octet-stream", path, this->TokenSecret, NULL, 0);
// Let's build the Header and call to curl
curl_global_init(CURL_GLOBAL_ALL);
@@ -681,20 +681,20 @@ void Cloud::CloudReader::DownloadFile(Cloud::CloudReader::FileEntry *entry)
if ( curl )
{
struct curl_slist *chunk = NULL;
char Url[256];
char URL[256];
// Let's build our own header
std::string strUrl(this->Url);
eraseSubStr(strUrl,"http://");
eraseSubStr(strUrl,"https://");
std::string strURL(this->URL);
eraseSubStr(strURL,"http://");
eraseSubStr(strURL,"https://");
chunk = Cloud::BuildHeaderAmzS3v2( strUrl.c_str(), this->TcpPort, this->AccessKey, RequestData);
chunk = Cloud::BuildHeaderAmzS3v2( strURL.c_str(), this->TCPPort, this->TokenAuth, RequestData);
delete RequestData;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
sprintf(Url,"%s:%s/%s/%s", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/%s", this->URL,this->TCPPort,
this->Bucket, entry->FileName);
curl_easy_setopt(curl, CURLOPT_URL, Url);
curl_easy_setopt(curl, CURLOPT_URL, URL);
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWrite_CallbackFunc_StdString);
@@ -783,7 +783,7 @@ void Cloud::CloudWriter::pushCloud(const char *FileName, const char *data, long
char path[1024];
sprintf(path, "/%s/%s", this->Bucket, FileName);
RequestData = Cloud::ComputeDigestAmzS3v2("PUT", "application/octet-stream", path, this->SecretKey, data, size);
RequestData = Cloud::ComputeDigestAmzS3v2("PUT", "application/octet-stream", path, this->TokenSecret, data, size);
// Let's build the Header and call to curl
curl_global_init(CURL_GLOBAL_ALL);
@@ -795,23 +795,23 @@ void Cloud::CloudWriter::pushCloud(const char *FileName, const char *data, long
if ( curl )
{
struct curl_slist *chunk = NULL;
char Url[256];
char URL[256];
// Let's build our own header
std::string strUrl(this->Url);
eraseSubStr(strUrl,"http://");
eraseSubStr(strUrl,"https://");
std::string strURL(this->URL);
eraseSubStr(strURL,"http://");
eraseSubStr(strURL,"https://");
chunk = Cloud::BuildHeaderAmzS3v2( strUrl.c_str(), this->TcpPort, this->AccessKey, RequestData);
chunk = Cloud::BuildHeaderAmzS3v2( strURL.c_str(), this->TCPPort, this->TokenAuth, RequestData);
delete RequestData;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
// Lets build the Url for our Curl call
// Lets build the URL for our Curl call
sprintf(Url,"%s:%s/%s/%s", this->Url,this->TcpPort,
sprintf(URL,"%s:%s/%s/%s", this->URL,this->TCPPort,
this->Bucket,FileName);
curl_easy_setopt(curl, CURLOPT_URL, Url);
curl_easy_setopt(curl, CURLOPT_URL, URL);
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
@@ -898,10 +898,10 @@ bool Cloud::Module::cloudSave(const char *BucketName)
if ( strcmp(BucketName, doc->Label.getValue()) != 0 )
doc->Label.setValue(BucketName);
Cloud::CloudWriter mywriter((const char*)this->Url.getStrValue().c_str(),
(const char*)this->AccessKey.getStrValue().c_str(),
(const char*)this->SecretKey.getStrValue().c_str(),
(const char*)this->TcpPort.getStrValue().c_str(),
Cloud::CloudWriter mywriter((const char*)this->URL.getStrValue().c_str(),
(const char*)this->TokenAuth.getStrValue().c_str(),
(const char*)this->TokenSecret.getStrValue().c_str(),
(const char*)this->TCPPort.getStrValue().c_str(),
BucketName);
mywriter.putNextEntry("Document.xml");
@@ -972,10 +972,10 @@ bool Cloud::Module::cloudRestore (const char *BucketName)
std::stringstream oss;
Cloud::CloudReader myreader((const char*)this->Url.getStrValue().c_str(),
(const char*)this->AccessKey.getStrValue().c_str(),
(const char*)this->SecretKey.getStrValue().c_str(),
(const char*)this->TcpPort.getStrValue().c_str(),
Cloud::CloudReader myreader((const char*)this->URL.getStrValue().c_str(),
(const char*)this->TokenAuth.getStrValue().c_str(),
(const char*)this->TokenSecret.getStrValue().c_str(),
(const char*)this->TCPPort.getStrValue().c_str(),
BucketName);
// we shall pass there the initial Document.xml file
+31 -31
View File
@@ -58,13 +58,13 @@ struct AmzData {
void eraseSubStr(std::string & Str, const std::string & toErase);
size_t CurlWrite_CallbackFunc_StdString(void *contents, size_t size, size_t nmemb, std::string *s);
struct AmzData *ComputeDigestAmzS3v2(char *operation, char *data_type, const char *target, const char *Secret, const char *ptr, long size);
struct curl_slist *BuildHeaderAmzS3v2(const char *Url, const char *TcpPort, const char *PublicKey, struct AmzData *Data);
struct curl_slist *BuildHeaderAmzS3v2(const char *URL, const char *TCPPort, const char *PublicKey, struct AmzData *Data);
char *MD5Sum(const char *ptr, long size);
class CloudAppExport CloudReader
{
public:
CloudReader(const char* Url, const char* AccessKey, const char* SecretKey, const char* TcpPort, const char* Bucket);
CloudReader(const char* URL, const char* AccessKey, const char* SecretKey, const char* TCPPort, const char* Bucket);
virtual ~CloudReader();
int file=0;
int continuation=0;
@@ -86,10 +86,10 @@ public:
protected:
std::list<Cloud::CloudReader::FileEntry*> FileList;
char* NextFileName;
const char* Url;
const char* TcpPort;
const char* AccessKey;
const char* SecretKey;
const char* URL;
const char* TCPPort;
const char* TokenAuth;
const char* TokenSecret;
const char* Bucket;
};
@@ -98,28 +98,28 @@ class Module : public Py::ExtensionModule<Module>
public:
Module() : Py::ExtensionModule<Module>("Cloud")
{
add_varargs_method("cloudurl",&Module::sCloudUrl,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("URL",&Module::sCloudURL,
"URL(string) -- Connect to a Cloud Storage service."
);
add_varargs_method("cloudaccesskey",&Module::sCloudAccessKey,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("TokenAuth",&Module::sCloudTokenAuth,
"TokenAuth(string) -- Token Authorization string."
);
add_varargs_method("cloudsecretkey",&Module::sCloudSecretKey,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("TokenSecret",&Module::sCloudTokenSecret,
"TokenSecret(string) -- Token Secret string."
);
add_varargs_method("cloudtcpport",&Module::sCloudTcpPort,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("TCPPort",&Module::sCloudTCPPort,
"TCPPort(string) -- Port number."
);
add_varargs_method("cloudsave",&Module::sCloudSave,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("Save",&Module::sCloudSave,
"Save(string) -- Save the active document to the Cloud."
);
add_varargs_method("cloudrestore",&Module::sCloudRestore,
"cloudurl(string) -- Connect to a Cloud Storage service."
add_varargs_method("Restore",&Module::sCloudRestore,
"Restore(string) -- Restore to the active document from the Cloud."
);
initialize("This module is the Cloud module."); // register with Python
@@ -127,18 +127,18 @@ public:
virtual ~Module() {}
App::PropertyString Url;
App::PropertyString TcpPort;
App::PropertyString AccessKey;
App::PropertyString SecretKey;
App::PropertyString URL;
App::PropertyString TCPPort;
App::PropertyString TokenAuth;
App::PropertyString TokenSecret;
bool cloudSave(const char* BucketName);
bool cloudRestore(const char* BucketName);
private:
Py::Object sCloudUrl (const Py::Tuple& args);
Py::Object sCloudAccessKey (const Py::Tuple& args);
Py::Object sCloudSecretKey (const Py::Tuple& args);
Py::Object sCloudTcpPort (const Py::Tuple& args);
Py::Object sCloudURL (const Py::Tuple& args);
Py::Object sCloudTokenAuth (const Py::Tuple& args);
Py::Object sCloudTokenSecret (const Py::Tuple& args);
Py::Object sCloudTCPPort (const Py::Tuple& args);
Py::Object sCloudSave (const Py::Tuple& args);
Py::Object sCloudRestore (const Py::Tuple& args);
@@ -158,7 +158,7 @@ class CloudAppExport CloudWriter : public Base::Writer
public:
int print=0;
char errorCode[1024]="";
CloudWriter(const char* Url, const char* AccessKey, const char* SecretKey, const char* TcpPort, const char* Bucket);
CloudWriter(const char* URL, const char* TokenAuth, const char* TokenSecret, const char* TCPPort, const char* Bucket);
virtual ~CloudWriter();
void pushCloud(const char *FileName, const char *data, long size);
void putNextEntry(const char* file);
@@ -173,10 +173,10 @@ public:
protected:
std::string FileName;
const char* Url;
const char* TcpPort;
const char* AccessKey;
const char* SecretKey;
const char* URL;
const char* TCPPort;
const char* TokenAuth;
const char* TokenSecret;
const char* Bucket;
std::stringstream FileStream;
};
+17
View File
@@ -49,6 +49,7 @@ SET(Draft_tests
SET(Draft_utilities
draftutils/__init__.py
draftutils/init_tools.py
draftutils/init_draft_statusbar.py
draftutils/utils.py
draftutils/gui_utils.py
draftutils/todo.py
@@ -63,6 +64,10 @@ SET(Draft_objects
draftobjects/orthoarray.py
draftobjects/polararray.py
draftobjects/arc_3points.py
draftobjects/draft_annotation.py
draftobjects/label.py
draftobjects/dimension.py
draftobjects/text.py
draftobjects/README.md
)
@@ -71,6 +76,10 @@ SET(Draft_view_providers
draftviewproviders/view_circulararray.py
draftviewproviders/view_orthoarray.py
draftviewproviders/view_polararray.py
draftviewproviders/view_draft_annotation.py
draftviewproviders/view_label.py
draftviewproviders/view_dimension.py
draftviewproviders/view_text.py
draftviewproviders/README.md
)
@@ -87,6 +96,14 @@ SET(Draft_GUI_tools
draftguitools/gui_snapper.py
draftguitools/gui_trackers.py
draftguitools/gui_edit.py
draftguitools/gui_lineops.py
draftguitools/gui_togglemodes.py
draftguitools/gui_groups.py
draftguitools/gui_grid.py
draftguitools/gui_heal.py
draftguitools/gui_dimension_ops.py
draftguitools/gui_lineslope.py
draftguitools/gui_arcs.py
draftguitools/README.md
)
+76 -1466
View File
File diff suppressed because it is too large Load Diff
+19 -1
View File
@@ -305,7 +305,7 @@ def findIntersection(edge1, edge2,
except:
return []
norm3 = vec1.cross(vec2)
if not DraftVecUtils.isNull(norm3) :
if not DraftVecUtils.isNull(norm3) and (norm3.x+norm3.y+norm3.z != 0):
k = ((pt3.z-pt1.z)*(vec2.x-vec2.y)+(pt3.y-pt1.y)*(vec2.z-vec2.x)+ \
(pt3.x-pt1.x)*(vec2.y-vec2.z))/(norm3.x+norm3.y+norm3.z)
vec1.scale(k,k,k)
@@ -1178,6 +1178,17 @@ def isReallyClosed(wire):
if DraftVecUtils.equals(v1,v2): return True
return False
def getSplineNormal(edge):
"""Find the normal of a BSpline edge"""
startPoint = edge.valueAt(edge.FirstParameter)
endPoint = edge.valueAt(edge.LastParameter)
midParameter = edge.FirstParameter + (edge.LastParameter - edge.FirstParameter)/2
midPoint = edge.valueAt(midParameter)
v1 = midPoint - startPoint
v2 = midPoint - endPoint
n = v1.cross(v2)
n.normalize()
return n
def getNormal(shape):
"""Find the normal of a shape, if possible."""
@@ -1189,11 +1200,18 @@ def getNormal(shape):
elif shape.ShapeType == "Edge":
if geomType(shape.Edges[0]) in ["Circle","Ellipse"]:
n = shape.Edges[0].Curve.Axis
elif geomType(edge) == "BSplineCurve" or \
geomType(edge) == "BezierCurve":
n = getSplineNormal(edge)
else:
for e in shape.Edges:
if geomType(e) in ["Circle","Ellipse"]:
n = e.Curve.Axis
break
elif geomType(e) == "BSplineCurve" or \
geomType(e) == "BezierCurve":
n = getSplineNormal(e)
break
e1 = vec(shape.Edges[0])
for i in range(1,len(shape.Edges)):
e2 = vec(shape.Edges[i])
+1 -2
View File
@@ -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"):
+47 -466
View File
@@ -77,6 +77,22 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"):
import draftguitools.gui_edit
import draftguitools.gui_selectplane
import draftguitools.gui_planeproxy
from draftguitools.gui_lineops import FinishLine
from draftguitools.gui_lineops import CloseLine
from draftguitools.gui_lineops import UndoLine
from draftguitools.gui_togglemodes import ToggleConstructionMode
from draftguitools.gui_togglemodes import ToggleContinueMode
from draftguitools.gui_togglemodes import ToggleDisplayMode
from draftguitools.gui_groups import AddToGroup
from draftguitools.gui_groups import SelectGroup
from draftguitools.gui_groups import SetAutoGroup
from draftguitools.gui_groups import Draft_AddConstruction
from draftguitools.gui_grid import ToggleGrid
from draftguitools.gui_heal import Heal
from draftguitools.gui_dimension_ops import Draft_FlipDimension
from draftguitools.gui_lineslope import Draft_Slope
from draftguitools.gui_arcs import Draft_Arc_3Points
import draftguitools.gui_arrays
# import DraftFillet
import drafttaskpanels.task_shapestring as task_shapestring
import drafttaskpanels.task_scale as task_scale
@@ -980,66 +996,6 @@ class CubicBezCurve(Line):
self.Activated()
class FinishLine:
"""a FreeCAD command to finish any running Line drawing operation"""
def Activated(self):
if (FreeCAD.activeDraftCommand != None):
if (FreeCAD.activeDraftCommand.featureName == "Line"):
FreeCAD.activeDraftCommand.finish(False)
def GetResources(self):
return {'Pixmap' : 'Draft_Finish',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finish line"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finishes a line without closing it")}
def IsActive(self):
if FreeCADGui.ActiveDocument:
return True
else:
return False
class CloseLine:
"""a FreeCAD command to close any running Line drawing operation"""
def Activated(self):
if (FreeCAD.activeDraftCommand != None):
if (FreeCAD.activeDraftCommand.featureName == "Line"):
FreeCAD.activeDraftCommand.finish(True)
def GetResources(self):
return {'Pixmap' : 'Draft_Lock',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Close Line"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Closes the line being drawn")}
def IsActive(self):
if FreeCADGui.ActiveDocument:
return True
else:
return False
class UndoLine:
"""a FreeCAD command to undo last drawn segment of a line"""
def Activated(self):
if (FreeCAD.activeDraftCommand != None):
if (FreeCAD.activeDraftCommand.featureName == "Line"):
FreeCAD.activeDraftCommand.undolast()
def GetResources(self):
return {'Pixmap' : 'Draft_Rotate',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undo last segment"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undoes the last drawn segment of the line being drawn")}
def IsActive(self):
if FreeCADGui.ActiveDocument:
return True
else:
return False
class Rectangle(Creator):
"""the Draft_Rectangle FreeCAD command definition"""
@@ -3067,6 +3023,8 @@ class Offset(Modifier):
['Draft.offset(FreeCAD.ActiveDocument.'+self.sel.Name+','+d+',copy='+str(copymode)+',occ='+str(occmode)+')',
'FreeCAD.ActiveDocument.recompute()'])
self.finish()
else:
FreeCAD.Console.PrintError(translate("draft","Offset direction is not defined. Please move the mouse on either side of the object first to indicate a direction")+"/n")
class Stretch(Modifier):
@@ -4179,30 +4137,6 @@ class Scale(Modifier):
for ghost in self.ghosts:
ghost.finalize()
class ToggleConstructionMode():
"""The Draft_ToggleConstructionMode FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_Construction',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggle Construction Mode"),
'Accel' : "C, M",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggles the Construction Mode for next objects.")}
def Activated(self):
FreeCADGui.draftToolBar.constrButton.toggle()
class ToggleContinueMode():
"""The Draft_ToggleContinueMode FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_Rotate',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggle Continue Mode"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggles the Continue Mode for next commands.")}
def Activated(self):
FreeCADGui.draftToolBar.toggleContinue()
class Drawing(Modifier):
"""The Draft Drawing command definition"""
@@ -4275,30 +4209,6 @@ class Drawing(Modifier):
return page
class ToggleDisplayMode():
"""The ToggleDisplayMode FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_SwitchMode',
'Accel' : "Shift+Space",
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Toggle display mode"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Swaps display mode of selected objects between wireframe and flatlines")}
def IsActive(self):
if FreeCADGui.Selection.getSelection():
return True
else:
return False
def Activated(self):
for obj in FreeCADGui.Selection.getSelection():
if obj.ViewObject.DisplayMode == "Flat Lines":
if "Wireframe" in obj.ViewObject.listDisplayModes():
obj.ViewObject.DisplayMode = "Wireframe"
elif obj.ViewObject.DisplayMode == "Wireframe":
if "Flat Lines" in obj.ViewObject.listDisplayModes():
obj.ViewObject.DisplayMode = "Flat Lines"
class SubelementHighlight(Modifier):
"""The Draft_SubelementHighlight FreeCAD command definition"""
@@ -4388,49 +4298,6 @@ class SubelementHighlight(Modifier):
# This can occur if objects have had graph changing operations
pass
class AddToGroup():
"""The AddToGroup FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_AddToGroup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Move to group..."),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Moves the selected object(s) to an existing group")}
def IsActive(self):
if FreeCADGui.Selection.getSelection():
return True
else:
return False
def Activated(self):
self.groups = ["Ungroup"]
self.groups.extend(Draft.getGroupNames())
self.labels = ["Ungroup"]
for g in self.groups:
o = FreeCAD.ActiveDocument.getObject(g)
if o: self.labels.append(o.Label)
self.ui = FreeCADGui.draftToolBar
self.ui.sourceCmd = self
self.ui.popupMenu(self.labels)
def proceed(self,labelname):
self.ui.sourceCmd = None
if labelname == "Ungroup":
for obj in FreeCADGui.Selection.getSelection():
try:
Draft.ungroup(obj)
except:
pass
else:
if labelname in self.labels:
i = self.labels.index(labelname)
g = FreeCAD.ActiveDocument.getObject(self.groups[i])
for obj in FreeCADGui.Selection.getSelection():
try:
g.addObject(obj)
except:
pass
class WireToBSpline(Modifier):
"""The Draft_Wire2BSpline FreeCAD command definition"""
@@ -4479,38 +4346,6 @@ class WireToBSpline(Modifier):
self.finish()
class SelectGroup():
"""The SelectGroup FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_SelectGroup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Select group"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Selects all objects with the same parents as this group")}
def IsActive(self):
if FreeCADGui.Selection.getSelection():
return True
else:
return False
def Activated(self):
sellist = []
sel = FreeCADGui.Selection.getSelection()
if len(sel) == 1:
if sel[0].isDerivedFrom("App::DocumentObjectGroup"):
cts = Draft.getGroupContents(FreeCADGui.Selection.getSelection())
for o in cts:
FreeCADGui.Selection.addSelection(o)
return
for ob in sel:
for child in ob.OutList:
FreeCADGui.Selection.addSelection(child)
for parent in ob.InList:
FreeCADGui.Selection.addSelection(parent)
for child in parent.OutList:
FreeCADGui.Selection.addSelection(child)
class Shape2DView(Modifier):
"""The Shape2DView FreeCAD command definition"""
@@ -4614,9 +4449,16 @@ class Draft2Sketch(Modifier):
class Array(Modifier):
"""The Shape2DView FreeCAD command definition"""
"""GuiCommand for the Draft_Array tool.
def __init__(self,use_link=False):
Parameters
----------
use_link: bool, optional
It defaults to `False`. If it is `True`, the created object
will be a `Link array`.
"""
def __init__(self, use_link=False):
Modifier.__init__(self)
self.use_link = use_link
@@ -4647,11 +4489,12 @@ class Array(Modifier):
'FreeCAD.ActiveDocument.recompute()'])
self.finish()
class LinkArray(Array):
"The Shape2DView FreeCAD command definition"
"""GuiCommand for the Draft_LinkArray tool."""
def __init__(self):
Array.__init__(self,True)
Array.__init__(self, use_link=True)
def GetResources(self):
return {'Pixmap' : 'Draft_LinkArray',
@@ -4820,17 +4663,6 @@ class Point(Creator):
if self.ui.continueMode:
self.Activated()
class ShowSnapBar():
"""The ShowSnapBar FreeCAD command definition"""
def GetResources(self):
return {'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ShowSnapBar", "Show Snap Bar"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ShowSnapBar", "Shows Draft snap toolbar")}
def Activated(self):
if hasattr(FreeCADGui,"Snapper"):
FreeCADGui.Snapper.show()
class Draft_Clone(Modifier):
"""The Draft Clone command definition"""
@@ -4880,45 +4712,6 @@ class Draft_Clone(Modifier):
ToDo.delay(FreeCADGui.runCommand, "Draft_Move")
class ToggleGrid():
"""The Draft ToggleGrid command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_Grid',
'Accel' : "G,R",
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleGrid", "Toggle Grid"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ToggleGrid", "Toggles the Draft grid on/off"),
'CmdType' : 'ForEdit'}
def Activated(self):
if hasattr(FreeCADGui,"Snapper"):
FreeCADGui.Snapper.setTrackers()
if FreeCADGui.Snapper.grid:
if FreeCADGui.Snapper.grid.Visible:
FreeCADGui.Snapper.grid.off()
FreeCADGui.Snapper.forceGridOff=True
else:
FreeCADGui.Snapper.grid.on()
FreeCADGui.Snapper.forceGridOff=False
class Heal():
"""The Draft Heal command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_Heal',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Heal", "Heal"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_Heal", "Heal faulty Draft objects saved from an earlier FreeCAD version")}
def Activated(self):
s = FreeCADGui.Selection.getSelection()
FreeCAD.ActiveDocument.openTransaction("Heal")
if s:
Draft.heal(s)
else:
Draft.heal()
FreeCAD.ActiveDocument.commitTransaction()
class Draft_Facebinder(Creator):
"""The Draft Facebinder command definition"""
@@ -4952,20 +4745,6 @@ class Draft_Facebinder(Creator):
FreeCAD.ActiveDocument.recompute()
self.finish()
class Draft_FlipDimension():
def GetResources(self):
return {'Pixmap' : 'Draft_FlipDimension',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_FlipDimension", "Flip Dimension"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_FlipDimension", "Flip the normal direction of a dimension")}
def Activated(self):
for o in FreeCADGui.Selection.getSelection():
if Draft.getType(o) in ["Dimension","AngularDimension"]:
FreeCAD.ActiveDocument.openTransaction("Flip dimension")
FreeCADGui.doCommand("FreeCAD.ActiveDocument."+o.Name+".Normal = FreeCAD.ActiveDocument."+o.Name+".Normal.negative()")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
class Mirror(Modifier):
"""The Draft_Mirror FreeCAD command definition"""
@@ -5091,117 +4870,6 @@ class Mirror(Modifier):
self.finish()
class Draft_Slope():
def GetResources(self):
return {'Pixmap' : 'Draft_Slope',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Slope", "Set Slope"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_Slope", "Sets the slope of a selected Line or Wire")}
def Activated(self):
if not FreeCADGui.Selection.getSelection():
return
for obj in FreeCADGui.Selection.getSelection():
if Draft.getType(obj) != "Wire":
FreeCAD.Console.PrintMessage(translate("draft", "This tool only works with Wires and Lines")+"\n")
return
w = QtGui.QWidget()
w.setWindowTitle(translate("Draft","Slope"))
layout = QtGui.QHBoxLayout(w)
label = QtGui.QLabel(w)
label.setText(translate("Draft", "Slope")+":")
layout.addWidget(label)
self.spinbox = QtGui.QDoubleSpinBox(w)
self.spinbox.setMinimum(-9999.99)
self.spinbox.setMaximum(9999.99)
self.spinbox.setSingleStep(0.01)
self.spinbox.setToolTip(translate("Draft", "Slope to give selected Wires/Lines: 0 = horizontal, 1 = 45deg up, -1 = 45deg down"))
layout.addWidget(self.spinbox)
taskwidget = QtGui.QWidget()
taskwidget.form = w
taskwidget.accept = self.accept
FreeCADGui.Control.showDialog(taskwidget)
def accept(self):
if hasattr(self,"spinbox"):
pc = self.spinbox.value()
FreeCAD.ActiveDocument.openTransaction("Change slope")
for obj in FreeCADGui.Selection.getSelection():
if Draft.getType(obj) == "Wire":
if len(obj.Points) > 1:
lp = None
np = []
for p in obj.Points:
if not lp:
lp = p
else:
v = p.sub(lp)
z = pc*FreeCAD.Vector(v.x,v.y,0).Length
lp = FreeCAD.Vector(p.x,p.y,lp.z+z)
np.append(lp)
obj.Points = np
FreeCAD.ActiveDocument.commitTransaction()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
class SetAutoGroup():
"""The SetAutoGroup FreeCAD command definition"""
def GetResources(self):
return {'Pixmap' : 'Draft_AutoGroup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AutoGroup", "AutoGroup"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AutoGroup", "Select a group to automatically add all Draft & Arch objects to")}
def IsActive(self):
if FreeCADGui.ActiveDocument:
return True
else:
return False
def Activated(self):
if hasattr(FreeCADGui,"draftToolBar"):
self.ui = FreeCADGui.draftToolBar
s = FreeCADGui.Selection.getSelection()
if len(s) == 1:
if (Draft.getType(s[0]) == "Layer") or \
( FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups",False) and \
(s[0].isDerivedFrom("App::DocumentObjectGroup") or (Draft.getType(s[0]) in ["Site","Building","Floor","BuildingPart",]))):
self.ui.setAutoGroup(s[0].Name)
return
self.groups = ["None"]
gn = [o.Name for o in FreeCAD.ActiveDocument.Objects if Draft.getType(o) == "Layer"]
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups",False):
gn.extend(Draft.getGroupNames())
if gn:
self.groups.extend(gn)
self.labels = [translate("draft","None")]
self.icons = [self.ui.getIcon(":/icons/button_invalid.svg")]
for g in gn:
o = FreeCAD.ActiveDocument.getObject(g)
if o:
self.labels.append(o.Label)
self.icons.append(o.ViewObject.Icon)
self.labels.append(translate("draft","Add new Layer"))
self.icons.append(self.ui.getIcon(":/icons/document-new.svg"))
self.ui.sourceCmd = self
from PySide import QtCore
pos = self.ui.autoGroupButton.mapToGlobal(QtCore.QPoint(0,self.ui.autoGroupButton.geometry().height()))
self.ui.popupMenu(self.labels,self.icons,pos)
def proceed(self,labelname):
self.ui.sourceCmd = None
if labelname in self.labels:
if labelname == self.labels[0]:
self.ui.setAutoGroup(None)
elif labelname == self.labels[-1]:
FreeCADGui.runCommand("Draft_Layer")
else:
i = self.labels.index(labelname)
self.ui.setAutoGroup(self.groups[i])
class Draft_Label(Creator):
"""The Draft_Label command definition"""
@@ -5272,6 +4940,7 @@ class Draft_Label(Creator):
FreeCAD.ActiveDocument.openTransaction("Create Label")
FreeCADGui.addModule("Draft")
FreeCADGui.doCommand("l = Draft.makeLabel("+tp+sel+"direction='"+direction+"',distance="+str(dist)+",labeltype='"+self.labeltype+"',"+pl+")")
FreeCADGui.doCommand("Draft.autogroup(l)")
FreeCAD.ActiveDocument.recompute()
FreeCAD.ActiveDocument.commitTransaction()
self.finish()
@@ -5336,97 +5005,25 @@ class Draft_Label(Creator):
self.create()
class Draft_AddConstruction():
def GetResources(self):
return {'Pixmap' : 'Draft_Construction',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddConstruction", "Add to Construction group"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_AddConstruction", "Adds the selected objects to the Construction group")}
def Activated(self):
import FreeCADGui
if hasattr(FreeCADGui,"draftToolBar"):
col = FreeCADGui.draftToolBar.getDefaultColor("constr")
col = (float(col[0]),float(col[1]),float(col[2]),0.0)
gname = Draft.getParam("constructiongroupname","Construction")
grp = FreeCAD.ActiveDocument.getObject(gname)
if not grp:
grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",gname)
for obj in FreeCADGui.Selection.getSelection():
grp.addObject(obj)
obrep = obj.ViewObject
if "TextColor" in obrep.PropertiesList:
obrep.TextColor = col
if "PointColor" in obrep.PropertiesList:
obrep.PointColor = col
if "LineColor" in obrep.PropertiesList:
obrep.LineColor = col
if "ShapeColor" in obrep.PropertiesList:
obrep.ShapeColor = col
if hasattr(obrep,"Transparency"):
obrep.Transparency = 80
class Draft_Arc_3Points:
def GetResources(self):
return {'Pixmap' : "Draft_Arc_3Points.svg",
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Arc_3Points", "Arc 3 points"),
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_Arc_3Points", "Creates an arc by 3 points"),
'Accel' : 'A,T'}
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False
def Activated(self):
self.points = []
self.normal = None
self.tracker = trackers.arcTracker()
self.tracker.autoinvert = False
if hasattr(FreeCAD,"DraftWorkingPlane"):
FreeCAD.DraftWorkingPlane.setup()
FreeCADGui.Snapper.getPoint(callback=self.getPoint,movecallback=self.drawArc)
def getPoint(self,point,info):
if not point: # cancelled
self.tracker.off()
return
if not(point in self.points): # avoid same point twice
self.points.append(point)
if len(self.points) < 3:
if len(self.points) == 2:
self.tracker.on()
FreeCADGui.Snapper.getPoint(last=self.points[-1],callback=self.getPoint,movecallback=self.drawArc)
else:
import draftobjects.arc_3points as arc3
if Draft.getParam("UsePartPrimitives",False):
arc3.make_arc_3points([self.points[0],
self.points[1],
self.points[2]], primitive=True)
else:
arc3.make_arc_3points([self.points[0],
self.points[1],
self.points[2]], primitive=False)
self.tracker.off()
FreeCAD.ActiveDocument.recompute()
def drawArc(self,point,info):
if len(self.points) == 2:
if point.sub(self.points[1]).Length > 0.001:
self.tracker.setBy3Points(self.points[0],self.points[1],point)
#---------------------------------------------------------------------------
# Snap tools
#---------------------------------------------------------------------------
import draftguitools.gui_snaps
from draftguitools.gui_snaps import Draft_Snap_Lock
from draftguitools.gui_snaps import Draft_Snap_Midpoint
from draftguitools.gui_snaps import Draft_Snap_Perpendicular
from draftguitools.gui_snaps import Draft_Snap_Grid
from draftguitools.gui_snaps import Draft_Snap_Intersection
from draftguitools.gui_snaps import Draft_Snap_Parallel
from draftguitools.gui_snaps import Draft_Snap_Endpoint
from draftguitools.gui_snaps import Draft_Snap_Angle
from draftguitools.gui_snaps import Draft_Snap_Center
from draftguitools.gui_snaps import Draft_Snap_Extension
from draftguitools.gui_snaps import Draft_Snap_Near
from draftguitools.gui_snaps import Draft_Snap_Ortho
from draftguitools.gui_snaps import Draft_Snap_Special
from draftguitools.gui_snaps import Draft_Snap_Dimensions
from draftguitools.gui_snaps import Draft_Snap_WorkingPlane
from draftguitools.gui_snaps import ShowSnapBar
#---------------------------------------------------------------------------
# Adds the icons & commands to the FreeCAD command manager, and sets defaults
@@ -5447,7 +5044,6 @@ class CommandArcGroup:
def IsActive(self):
return not FreeCAD.ActiveDocument is None
FreeCADGui.addCommand('Draft_Arc',Arc())
FreeCADGui.addCommand('Draft_Arc_3Points',Draft_Arc_3Points())
FreeCADGui.addCommand('Draft_ArcTools', CommandArcGroup())
FreeCADGui.addCommand('Draft_Text',Text())
FreeCADGui.addCommand('Draft_Rectangle',Rectangle())
@@ -5492,27 +5088,12 @@ FreeCADGui.addCommand('Draft_Clone',Draft_Clone())
FreeCADGui.addCommand('Draft_PathArray',PathArray())
FreeCADGui.addCommand('Draft_PathLinkArray',PathLinkArray())
FreeCADGui.addCommand('Draft_PointArray',PointArray())
FreeCADGui.addCommand('Draft_Heal',Heal())
FreeCADGui.addCommand('Draft_Mirror',Mirror())
FreeCADGui.addCommand('Draft_Slope',Draft_Slope())
FreeCADGui.addCommand('Draft_Stretch',Stretch())
# context commands
FreeCADGui.addCommand('Draft_FinishLine',FinishLine())
FreeCADGui.addCommand('Draft_CloseLine',CloseLine())
FreeCADGui.addCommand('Draft_UndoLine',UndoLine())
FreeCADGui.addCommand('Draft_ToggleConstructionMode',ToggleConstructionMode())
FreeCADGui.addCommand('Draft_ToggleContinueMode',ToggleContinueMode())
FreeCADGui.addCommand('Draft_ApplyStyle',ApplyStyle())
FreeCADGui.addCommand('Draft_ToggleDisplayMode',ToggleDisplayMode())
FreeCADGui.addCommand('Draft_AddToGroup',AddToGroup())
FreeCADGui.addCommand('Draft_SelectGroup',SelectGroup())
FreeCADGui.addCommand('Draft_Shape2DView',Shape2DView())
FreeCADGui.addCommand('Draft_ShowSnapBar',ShowSnapBar())
FreeCADGui.addCommand('Draft_ToggleGrid',ToggleGrid())
FreeCADGui.addCommand('Draft_FlipDimension',Draft_FlipDimension())
FreeCADGui.addCommand('Draft_AutoGroup',SetAutoGroup())
FreeCADGui.addCommand('Draft_AddConstruction',Draft_AddConstruction())
# a global place to look for active draft Command
FreeCAD.activeDraftCommand = None
+16 -6
View File
@@ -82,10 +82,6 @@ class DraftWorkbench(FreeCADGui.Workbench):
import DraftTools
import DraftGui
import DraftFillet
from draftguitools import gui_circulararray
from draftguitools import gui_polararray
from draftguitools import gui_orthoarray
from draftguitools import gui_arrays
FreeCADGui.addLanguagePath(":/translations")
FreeCADGui.addIconPath(":/icons")
except Exception as exc:
@@ -102,11 +98,13 @@ class DraftWorkbench(FreeCADGui.Workbench):
self.context_commands = it.get_draft_context_commands()
self.line_commands = it.get_draft_line_commands()
self.utility_commands = it.get_draft_utility_commands()
self.utility_small = it.get_draft_small_commands()
# Set up toolbars
self.appendToolbar(QT_TRANSLATE_NOOP("Draft", "Draft creation tools"), self.drawing_commands)
self.appendToolbar(QT_TRANSLATE_NOOP("Draft", "Draft annotation tools"), self.annotation_commands)
self.appendToolbar(QT_TRANSLATE_NOOP("Draft", "Draft modification tools"), self.modification_commands)
self.appendToolbar(QT_TRANSLATE_NOOP("Draft", "Draft utility tools"), self.utility_small)
# Set up menus
self.appendMenu(QT_TRANSLATE_NOOP("Draft", "&Drafting"), self.drawing_commands)
@@ -118,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"))
@@ -131,6 +130,8 @@ class DraftWorkbench(FreeCADGui.Workbench):
FreeCADGui.draftToolBar.Activated()
if hasattr(FreeCADGui, "Snapper"):
FreeCADGui.Snapper.show()
import draftutils.init_draft_statusbar as dsb
dsb.show_draft_statusbar()
FreeCAD.Console.PrintLog("Draft workbench activated.\n")
def Deactivated(self):
@@ -139,6 +140,8 @@ class DraftWorkbench(FreeCADGui.Workbench):
FreeCADGui.draftToolBar.Deactivated()
if hasattr(FreeCADGui, "Snapper"):
FreeCADGui.Snapper.hide()
import draftutils.init_draft_statusbar as dsb
dsb.hide_draft_statusbar()
FreeCAD.Console.PrintLog("Draft workbench deactivated.\n")
def ContextMenu(self, recipient):
@@ -152,8 +155,15 @@ class DraftWorkbench(FreeCADGui.Workbench):
else:
self.appendContextMenu("Draft", self.drawing_commands)
else:
if FreeCAD.activeDraftCommand.featureName == translate("draft","Line"):
# BUG: line subcommands are not usable while another command is active
if FreeCAD.activeDraftCommand.featureName in (translate("draft", "Line"),
translate("draft", "Wire"),
translate("draft", "Polyline"),
translate("draft", "BSpline"),
translate("draft", "BezCurve"),
translate("draft", "CubicBezCurve")):
# BUG: the line subcommands are in fact listed
# in the context menu, but they are de-activated
# so they don't work.
self.appendContextMenu("", self.line_commands)
else:
if FreeCADGui.Selection.getSelection():
+6
View File
@@ -1,8 +1,10 @@
<RCC>
<qresource>
<file>icons/Draft_2DShapeView.svg</file>
<file>icons/Draft_AddConstruction.svg</file>
<file>icons/Draft_AddPoint.svg</file>
<file>icons/Draft_AddToGroup.svg</file>
<file>icons/Draft_Annotation_Style.svg</file>
<file>icons/Draft_Apply.svg</file>
<file>icons/Draft_Arc.svg</file>
<file>icons/Draft_Arc_3Points.svg</file>
@@ -19,6 +21,7 @@
<file>icons/Draft_CircularArray.svg</file>
<file>icons/Draft_Clone.svg</file>
<file>icons/Draft_Construction.svg</file>
<file>icons/Draft_Continue.svg</file>
<file>icons/Draft_CubicBezCurve.svg</file>
<file>icons/Draft_Cursor.svg</file>
<file>icons/Draft_DelPoint.svg</file>
@@ -66,6 +69,7 @@
<file>icons/Draft_SelectGroup.svg</file>
<file>icons/Draft_SelectPlane.svg</file>
<file>icons/Draft_ShapeString.svg</file>
<file>icons/Draft_ShapeString_tree.svg</file>
<file>icons/Draft_Slope.svg</file>
<file>icons/Draft_Snap.svg</file>
<file>icons/Draft_Split.svg</file>
@@ -149,6 +153,7 @@
<file>translations/Draft_zh-CN.qm</file>
<file>translations/Draft_zh-TW.qm</file>
<file>ui/preferences-draft.ui</file>
<file>ui/preferences-draftinterface.ui</file>
<file>ui/preferences-draftsnap.ui</file>
<file>ui/preferences-drafttexts.ui</file>
<file>ui/preferences-draftvisual.ui</file>
@@ -161,5 +166,6 @@
<file>ui/TaskPanel_PolarArray.ui</file>
<file>ui/TaskSelectPlane.ui</file>
<file>ui/TaskShapeString.ui</file>
<file>ui/dialog_AnnotationStyleEditor.ui</file>
</qresource>
</RCC>
@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64px"
height="64px"
id="svg3611"
version="1.1">
<defs
id="defs3613">
<linearGradient
id="linearGradient3825">
<stop
style="stop-color:#34e0e2;stop-opacity:1;"
offset="0"
id="stop3827" />
<stop
style="stop-color:#34e0e2;stop-opacity:0;"
offset="1"
id="stop3829" />
</linearGradient>
<linearGradient
id="linearGradient3817">
<stop
style="stop-color:#06989a;stop-opacity:1;"
offset="0"
id="stop3819" />
<stop
style="stop-color:#06989a;stop-opacity:0;"
offset="1"
id="stop3821" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient4292"
id="linearGradient4125"
gradientUnits="userSpaceOnUse"
x1="657.42859"
y1="92.117249"
x2="696.53217"
y2="92.117249" />
<linearGradient
id="linearGradient4292">
<stop
style="stop-color:#62d9c5;stop-opacity:1;"
offset="0"
id="stop4294" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop4296" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient4300"
id="linearGradient4127"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.99534433,0.09638295,-0.09638295,0.99534433,12.979471,-62.75841)"
x1="651.69366"
y1="108.379"
x2="677.37708"
y2="108.379" />
<linearGradient
id="linearGradient4300">
<stop
style="stop-color:#dd4100;stop-opacity:1;"
offset="0"
id="stop4302" />
<stop
style="stop-color:#a80101;stop-opacity:1;"
offset="1"
id="stop4304" />
</linearGradient>
<linearGradient
y2="108.379"
x2="677.37708"
y1="108.379"
x1="651.69366"
gradientTransform="matrix(0.99534433,0.09638295,-0.09638295,0.99534433,12.979471,-62.75841)"
gradientUnits="userSpaceOnUse"
id="linearGradient3609"
xlink:href="#linearGradient4300" />
<linearGradient
xlink:href="#linearGradient4292-4"
id="linearGradient3001-0"
gradientUnits="userSpaceOnUse"
x1="657.42859"
y1="92.117249"
x2="696.53217"
y2="92.117249"
gradientTransform="matrix(0.93342445,0.4455918,-0.45023909,0.92378981,-552.52671,-364.08327)" />
<linearGradient
id="linearGradient4292-4">
<stop
style="stop-color:#62d9c5;stop-opacity:1;"
offset="0"
id="stop4294-4" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop4296-4" />
</linearGradient>
<linearGradient
y2="92.117249"
x2="696.53217"
y1="92.117249"
x1="657.42859"
gradientTransform="matrix(0.93342445,0.4455918,-0.45023909,0.92378981,-527.86118,-368.62645)"
gradientUnits="userSpaceOnUse"
id="linearGradient3807"
xlink:href="#linearGradient4292-4" />
<linearGradient
xlink:href="#linearGradient3817"
id="linearGradient3823"
x1="17"
y1="37"
x2="15"
y2="32"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#linearGradient3825"
id="linearGradient3831"
x1="40"
y1="21"
x2="47"
y2="38"
gradientUnits="userSpaceOnUse" />
</defs>
<g
transform="translate(0,6)"
id="layer1">
<path
style="fill:#06989a;fill-opacity:1;stroke:#042a2a;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 59.662253,17.123968 C 54.202538,10.619145 19,23 19,23 l 20,22 c 0,0 27.92132,-19.227416 20.662253,-27.876032 z"
id="path3498" />
<path
style="fill:url(#linearGradient3831);fill-opacity:1;stroke:#34e0e2;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 58,18 C 54.434171,13.753987 22.586852,23.947813 22.586852,23.947813 L 39.287029,42.334868 C 39.287029,42.334868 63.5322,24.58747 58,18 Z"
id="path3498-3" />
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#16d0d2;fill-opacity:1;fill-rule:nonzero;stroke:#042a2a;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
id="rect3772"
width="3"
height="8"
x="30"
y="25" />
<path
style="fill:#34e0e2;fill-opacity:1;stroke:#042a2a;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 33,25 c -3,-1 -20,4 -24,6 -4,2 -6,5 -6,8 0,3 4,4 10,2 6,-2 20,-11 20,-16 z"
id="path3500" />
<path
style="fill:url(#linearGradient3823);fill-opacity:1;stroke:#34e0e2;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 29.98637,26.883398 c -4.889547,0.05412 -16.07494,4.168831 -19.314168,5.536675 -3.2392271,1.367845 -5.7198849,4.18225 -5.7198849,6.234016 0,2.051766 3.2515281,1.923846 7.8889579,0.285389 4.637429,-1.638458 15.312301,-8.882481 17.145095,-12.05608 z"
id="path3500-6" />
</g>
<metadata
id="metadata3867">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[vocx]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_AddConstruction.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson, [wmayer]</dc:title>
</cc:Agent>
</dc:contributor>
<dc:subject>
<rdf:Bag>
<rdf:li>trowel</rdf:li>
<rdf:li>tool</rdf:li>
<rdf:li>plus sign</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>A trowel, and a plus sign</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:none;stroke:#042a2a;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 6.05697,18.049229 H 29.844572"
id="path853" />
<path
id="path851"
d="M 17.950771,6.1554283 V 29.94303"
style="fill:none;stroke:#042a2a;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path847"
d="M 6.05697,18.049229 H 29.844572"
style="fill:none;stroke:#34e0e2;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#34e0e2;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 17.950771,6.1554283 V 29.94303"
id="path849" />
<path
id="path855"
d="M 17.950771,6.1554283 V 29.94303"
style="fill:none;stroke:#179a9b;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#0aa0a2;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 6.05697,18.049229 H 29.844572"
id="path857" />
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64px"
height="64px"
id="svg4594"
version="1.1">
<defs
id="defs4596">
<linearGradient
id="linearGradient3792">
<stop
style="stop-color:#fce94f;stop-opacity:1"
offset="0"
id="stop3794" />
<stop
style="stop-color:#c4a000;stop-opacity:1"
offset="1"
id="stop3796" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3792"
id="linearGradient3806"
x1="4115.4229"
y1="1473.5258"
x2="4157.8677"
y2="1734.2576"
gradientUnits="userSpaceOnUse" />
</defs>
<g
transform="matrix(-1,0,0,1,59.999998,0)"
id="layer1">
<g
id="g3491"
transform="matrix(0.1649204,0,0,0.1649204,-643.71716,-232.01446)">
<path
id="text2714"
d="m 4151.8039,1655.4317 h -109.1436 l -33.475,40.7475 c -3.5157,5.2084 -2.3853,4.766 -2.9061,7.7608 0,6.0635 3.9238,9.9135 12.127,12.127 v 12.1271 h -97.0165 v -12.1271 c 5.4688,-1.0416 14.9354,-3.4593 18.1906,-6.0635 3.2552,-2.6042 6.0635,-6.0635 12.1271,-12.1271 l 139.4612,-200.0965 c 4.4269,-6.5102 5.282,-8.2206 6.0635,-12.1271 1.3019,-5.9893 0.9676,-9.5226 -6.0635,-12.127 v -12.1271 h 97.0165 v 12.1271 c -12.1271,0 -17.1492,0.595 -18.1906,6.0635 -0.7815,3.7763 -0.6532,6.3037 0.9096,11.1211 l 60.3515,205.4688 c 2.6039,8.724 2.8332,10.5692 5.4377,13.8243 2.6039,3.1251 6.9184,5.0219 12.1271,6.0635 v 12.1271 h -97.0165 v -12.1271 c 8.9841,-1.4322 10.8248,-5.4864 12.1271,-12.127 0.5206,-2.7344 1.5622,-6.7885 0,-12.1271 l -12.1271,-36.3812 m 0,-36.3812 -24.2541,-97.0165 -72.7624,97.0165 h 97.0165"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3806);fill-opacity:1;stroke:#302b00;stroke-width:12.1270628;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="m 4159.5466,1643.3046 h -122.0515 l -36.1634,43.7572 c -8.9781,11.6058 -6.5743,17.6706 -7.5981,18.9453 0,5.6501 9.2106,8.5485 16.6543,10.6111 l 1.2403,-0.1378 -78.1114,-0.4134 -3.7208,2.894 c 4.9624,-0.9706 23.3368,-5.4284 26.2906,-7.855 2.9538,-2.4267 7.4313,-8.4063 12.2445,-15.4346 l 117.1771,-168.1251 c 4.017,-6.0664 15.8176,-22.4055 19.0073,-28.5262 3.3862,-5.5809 9.1465,-22.5163 2.7663,-24.9431 l -10.1978,-0.5513 h 85.1396 l -13.0918,-0.6889 c -11.0041,0 -10.738,5.2398 -11.5452,9.233 -0.709,3.5189 0.6477,7.8032 2.0658,12.2922 l 55.5901,189.1168 c 2.3628,8.1291 2.4331,9.7107 3.8319,14.9489 3.6029,9.9401 1.3166,12.3967 6.0431,13.3673 l 17.6394,4.272 h -84.8895 l 12.5405,-6.2013 c 4.9826,-8.914 5.1625,-6.4905 6.3443,-12.6783 0.4724,-2.548 -2.5789,-12.6647 -3.9965,-17.6393 l -12.9335,-35.4166 m 4.4613,-18.4109 -33.36,-122.5662 -89.8152,122.4284 122.0295,0.024"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#fce94f;stroke-width:12.1270628;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="m 4151.8039,1655.4317 h -109.1436 l -33.4751,40.7475 c -3.5156,5.2084 -2.3852,4.766 -2.9061,7.7608 0,6.0635 3.9239,9.9135 12.1271,12.127 v 12.1271 h -97.0165 v -12.1271 c 5.4688,-1.0416 14.9354,-3.4593 18.1906,-6.0635 3.2552,-2.6042 6.0635,-6.0635 12.1271,-12.1271 l 139.4611,-200.0965 c 4.4269,-6.5102 5.2821,-8.2206 6.0636,-12.1271 1.3019,-5.9893 0.9676,-9.5226 -6.0636,-12.127 v -12.1271 h 97.0165 v 12.1271 c -12.127,0 -17.1492,0.595 -18.1906,6.0635 -0.7814,3.7763 -0.6531,6.3037 0.9097,11.1211 l 60.3514,205.4688 c 2.6039,8.724 2.8333,10.5692 5.4378,13.8243 2.6038,3.1251 6.9183,5.0219 12.1271,6.0635 v 12.1271 h -97.0165 v -12.1271 c 8.984,-1.4322 10.8247,-5.4864 12.127,-12.127 0.5207,-2.7344 1.5623,-6.7885 0,-12.1271 l -12.127,-36.3812 m 0,-36.3812 -24.2542,-97.0165 -72.7623,97.0165 h 97.0165"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#302b00;stroke-width:12.1270628;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
<metadata
id="metadata5750">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_Text.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
<dc:description>The capital letter A, slightly italicized</dc:description>
<dc:subject>
<rdf:Bag>
<rdf:li>A</rdf:li>
<rdf:li>letter</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1-9"
transform="translate(-2.826126,-22.844604)">
<g
transform="matrix(0,0.98259094,-1,0,156.35711,-405.08726)"
id="g3973">
<path
style="fill:#ffffff;stroke:#302b00;stroke-width:2.01763964;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 477.22051,113.0061 5.08859,17 -17.3012,-5 -2.03543,-4 10.17717,-10 z"
id="path3969" />
<path
style="fill:#c4a000;stroke:none"
d="m 456.86616,93.006094 -4.07087,4 20.35435,20.000006 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843" />
<path
style="fill:#edd400;stroke:#c4a000;stroke-linejoin:round"
d="m 452.79529,97.006087 -4.07087,4.000013 20.35435,20 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843-7" />
<path
style="fill:#fce94f;stroke:none"
d="m 448.72442,101.0061 -4.07087,4 20.35435,20 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843-5" />
<path
style="fill:none;stroke:#302b00;stroke-width:2.01763964;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 456.86616,93.006097 -4.07087,4 20.35435,20.000003 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843-5-6" />
<path
style="fill:none;stroke:#302b00;stroke-width:2.01763964;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 452.79529,97.006097 -4.07087,4.000003 20.35435,20 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843-5-6-2" />
<path
style="fill:none;stroke:#302b00;stroke-width:2.01763964;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 448.72442,101.0061 -4.07087,4 20.35435,20 c 0,-3 1.01772,-4 4.07087,-4 z"
id="path3843-5-6-9" />
<path
style="fill:#302b00;stroke:none;stroke-width:1.00881982px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 479.25595,122.0061 c -1.01772,0 -4.058,3.26642 -4.49502,5.75019 l 7.54817,2.24981 -2.28992,-7.91665 v 0"
id="path3971" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64px"
height="64px"
id="svg3074"
version="1.1">
<defs
id="defs3076">
<linearGradient
id="linearGradient3841">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3843" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3845" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3841"
id="linearGradient3863"
gradientUnits="userSpaceOnUse"
x1="3709.3296"
y1="1286.7291"
x2="3935.5251"
y2="1076.6174" />
<linearGradient
xlink:href="#linearGradient3895"
id="linearGradient3909"
x1="43"
y1="22"
x2="48"
y2="44"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3895">
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="0"
id="stop3897" />
<stop
style="stop-color:#204a87;stop-opacity:1;"
offset="1"
id="stop3899" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0,-1.4500001,1.4705882,0,-15.05882,91.45)"
y2="36.079998"
x2="21.689653"
y1="29.279999"
x1="56.172409"
gradientUnits="userSpaceOnUse"
id="linearGradient3036"
xlink:href="#linearGradient3895" />
<linearGradient
y2="36.079998"
x2="34.103447"
y1="29.279999"
x1="56.172409"
gradientTransform="matrix(0,-1.4500001,1.4705882,0,-15.05882,91.45)"
gradientUnits="userSpaceOnUse"
id="linearGradient869"
xlink:href="#linearGradient3895" />
<linearGradient
y2="36.079998"
x2="34.103447"
y1="29.279999"
x1="56.172409"
gradientTransform="matrix(0,-1.4500001,1.4705882,0,-15.05882,78.450001)"
gradientUnits="userSpaceOnUse"
id="linearGradient896"
xlink:href="#linearGradient3895" />
</defs>
<metadata
id="metadata5801">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[vocx]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_Continue.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson, [wmayer]</dc:title>
</cc:Agent>
</dc:contributor>
<dc:subject>
<rdf:Bag>
<rdf:li>arrow</rdf:li>
<rdf:li>double</rdf:li>
<rdf:li>right</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>A large blue arrow pointing to the right, and another one following it.</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g867"
transform="rotate(90,24.500001,24.5)">
<path
id="path892"
d="m 7.0000006,16.100001 14.0000004,-0.1 v 15 h 22 v -15 L 57,16.100001 32,-9.9999989 Z"
style="fill:url(#linearGradient896);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:1.99999988;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path894"
d="m 12.000001,14.000001 h 11 v 15 h 18 v -15 h 11 L 32.172062,-6.9999924 Z"
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:url(#linearGradient869);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:1.99999988;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.0000006,29.1 21.000001,29 v 15 h 22 V 29 L 57,29.1 32,3 Z"
id="path863" />
<path
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.000001,27 h 11 v 15 h 18 V 27 h 11 L 32.172062,6.000006 Z"
id="path865" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64px"
height="64px"
id="svg4594"
version="1.1">
<defs
id="defs4596">
<linearGradient
id="linearGradient3811">
<stop
style="stop-color:#c4a000;stop-opacity:1"
offset="0"
id="stop3813" />
<stop
style="stop-color:#fce94f;stop-opacity:1"
offset="1"
id="stop3815" />
</linearGradient>
<linearGradient
id="linearGradient3788">
<stop
style="stop-color:#c4a000;stop-opacity:1"
offset="0"
id="stop3790" />
<stop
style="stop-color:#fce94f;stop-opacity:1"
offset="1"
id="stop3792" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3788"
id="linearGradient3794"
x1="36.046734"
y1="50.470772"
x2="20.47628"
y2="11.793966"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#linearGradient3895"
id="linearGradient3817"
x1="23.561838"
y1="14.486353"
x2="42.853626"
y2="57.577202"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient6349">
<stop
id="stop6351"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop6353"
offset="1"
style="stop-color:#000000;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3377">
<stop
id="stop3379"
offset="0"
style="stop-color:#0019a3;stop-opacity:1;" />
<stop
id="stop3381"
offset="1"
style="stop-color:#0069ff;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(-1,0,0,1,2199.356,0)"
gradientUnits="userSpaceOnUse"
y2="1190.875"
x2="1267.9062"
y1="1190.875"
x1="901.1875"
id="linearGradient3383"
xlink:href="#linearGradient3377" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-1.4307499,-1.3605156e-7,-1.202713e-8,0.1264801,2674.7488,1244.2826)"
r="194.40614"
fy="1424.4465"
fx="1103.6399"
cy="1424.4465"
cx="1103.6399"
id="radialGradient6355"
xlink:href="#linearGradient6349" />
<radialGradient
xlink:href="#linearGradient6349"
id="radialGradient6355-6"
cx="1103.6399"
cy="1424.4465"
fx="1103.6399"
fy="1424.4465"
r="194.40614"
gradientTransform="matrix(-1.4307499,-1.3605156e-7,-1.202713e-8,0.1264801,2674.7488,1244.2826)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3354">
<stop
id="stop3356"
offset="0"
style="stop-color:#2157c7;stop-opacity:1;" />
<stop
id="stop3358"
offset="1"
style="stop-color:#6daaff;stop-opacity:1;" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3895"
id="linearGradient3036"
gradientUnits="userSpaceOnUse"
x1="56.172409"
y1="29.279999"
x2="21.689653"
y2="36.079998"
gradientTransform="matrix(0,-1.4500001,1.4705882,0,-15.05882,91.45)" />
<linearGradient
id="linearGradient3895">
<stop
id="stop3897"
offset="0"
style="stop-color:#729fcf;stop-opacity:1;" />
<stop
id="stop3899"
offset="1"
style="stop-color:#204a87;stop-opacity:1;" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3895"
id="linearGradient3918-3"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,-0.58000003,0.58823527,0,13.176471,38.379999)"
x1="56.172409"
y1="29.279999"
x2="21.689653"
y2="36.079998" />
<linearGradient
xlink:href="#linearGradient3895"
id="linearGradient3029-6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.58000003,0,0,0.58823527,25.620001,13.176471)"
x1="56.172409"
y1="29.279999"
x2="21.689653"
y2="36.079998" />
<linearGradient
y2="52.400002"
x2="-23.482759"
y1="11.599999"
x1="45.482754"
gradientTransform="matrix(0,-0.58000003,0.58823527,0,13.176471,38.379999)"
gradientUnits="userSpaceOnUse"
id="linearGradient3154"
xlink:href="#linearGradient3895" />
<linearGradient
y2="66"
x2="-9.6896563"
y1="-2.0000007"
x1="31.689651"
gradientTransform="matrix(-0.58000003,0,0,0.58823527,38.379999,13.176471)"
gradientUnits="userSpaceOnUse"
id="linearGradient3156"
xlink:href="#linearGradient3895" />
<linearGradient
y2="66"
x2="31.689651"
y1="-2.0000007"
x1="-9.6896563"
gradientTransform="matrix(0.58000003,0,0,0.58823527,25.620001,13.176471)"
gradientUnits="userSpaceOnUse"
id="linearGradient3158"
xlink:href="#linearGradient3895" />
<linearGradient
y2="52.400002"
x2="45.482754"
y1="11.599999"
x1="-23.482759"
gradientTransform="matrix(0,0.58000003,0.58823527,0,13.176471,25.620001)"
gradientUnits="userSpaceOnUse"
id="linearGradient3160"
xlink:href="#linearGradient3895" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="52"
x2="44"
y1="12"
x1="20"
id="linearGradient3936"
xlink:href="#linearGradient3895" />
</defs>
<g
transform="matrix(1.0012484,0,0,1.2260559,45.70983,21.37906)"
style="fill:#729fcf;stroke:#0b1521;stroke-width:1.80511105;stroke-opacity:1;fill-opacity:1"
id="g4061">
<g
id="text3103"
style="color:#000000;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80.06713104px;line-height:125%;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Italic';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#729fcf;stroke:#0b1521;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate;fill-opacity:1"
transform="scale(1.258685,0.79447996)">
<path
id="path2997"
style="fill:#729fcf;stroke:#0b1521;marker:none;stroke-opacity:1;fill-opacity:1"
d="m -32.302817,38.622148 4.378671,-19.664925 h 1.32924 c -0.182451,1.902653 -0.273673,3.479495 -0.273667,4.730529 -6e-6,3.570712 1.140271,6.476791 3.420836,8.718247 2.280547,2.241465 5.258301,3.362195 8.933272,3.362194 3.414299,1e-6 6.0076165,-1.049054 7.7799602,-3.14717 1.7722914,-2.098107 2.6584502,-4.515496 2.658479,-7.252174 -2.88e-5,-1.772306 -0.404013,-3.388243 -1.2119537,-4.847815 -1.2250117,-2.163253 -4.4959801,-5.968523 -9.8129145,-11.4158215 -2.580304,-2.606321 -4.235336,-4.5480512 -4.965101,-5.8251965 -1.198933,-2.1111088 -1.798394,-4.3265058 -1.798383,-6.6461974 -1.1e-5,-3.7009753 1.381354,-6.8676896 4.1441,-9.5001526 2.762715,-2.632363 6.30735,-3.948569 10.6339157,-3.948623 1.4595298,5.4e-5 2.840895,0.143403 4.1440995,0.430048 0.8079384,0.156434 2.28052579,0.677704 4.4177666,1.563811 1.5116482,0.599512 2.34568,0.925306 2.5020978,0.977382 0.3648521,0.07824 0.7688362,0.117337 1.2119536,0.117286 0.7558033,5.1e-5 1.4073906,-0.195425 1.954764,-0.586429 0.5472933,-0.390901 1.1858489,-1.224933 1.9156686,-2.502098 H 10.545608 L 6.4796994,0.77791813 H 5.15046 C 5.2546759,-0.78585482 5.3068029,-2.0499342 5.3068411,-3.0143239 5.3068029,-6.1679662 4.2642632,-8.748252 2.1792188,-10.755189 c -2.08511451,-2.006841 -4.834813,-3.010285 -8.2491039,-3.010337 -2.71063,5.2e-5 -4.9129949,0.794988 -6.6071019,2.384812 -1.694147,1.5899223 -2.541211,3.4273986 -2.541193,5.5124347 -1.8e-5,1.8244878 0.5408,3.564226 1.622454,5.21921976 1.081615,1.65506984 3.570679,4.35264144 7.467198,8.09272274 3.8964655,3.7401408 6.41810849,6.6592528 7.5649365,8.7573428 1.1467594,2.098132 1.7201562,4.333077 1.7201922,6.70484 -3.6e-5,2.684554 -0.7102662,5.284388 -2.1306927,7.799508 -1.42049421,2.515134 -3.4599625,4.46338 -6.1184111,5.844744 -2.6585041,1.381366 -5.5645839,2.072049 -8.7182469,2.07205 -1.563829,-1e-6 -3.023385,-0.14335 -4.378672,-0.430048 -1.355316,-0.286699 -3.518586,-0.990414 -6.489816,-2.111145 -1.016484,-0.390951 -1.863548,-0.586427 -2.541193,-0.586429 -1.537752,2e-6 -2.736672,1.042541 -3.596766,3.127622 z" />
</g>
</g>
<text
id="text3097"
y="19.773684"
x="15.684914"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#edd400;fill-opacity:1;stroke:#302b00;stroke-width:2"
xml:space="preserve"><tspan
style="font-size:40px;line-height:1.25;font-family:sans-serif"
y="19.773684"
x="15.684914"
id="tspan3099"> </tspan></text>
<path
id="path2997-0"
d="m 29.3519,8.1347804 c -6.562684,1.410881 -12.046817,9.5244946 -7.125,15.3437496 6.101664,7.122025 16.730075,11.103607 20.25,20.375 1.126065,4.464765 -1.197115,9.158945 -4.84375,11.71875 7.159093,-1.69687 13.379117,-10.628 8.40625,-17.25 -5.88152,-7.936101 -17.468774,-10.974591 -21.125,-20.625 -1.1145,-3.968568 1.528727,-7.9065687 4.90625,-9.7187496 z M 55.421432,9.446023 c -1.454337,1.571174 -6.612341,-0.6947496 -3.340117,1.766067 1.949211,2.859194 1.686574,5.783269 2.487426,0.446825 0.281039,-0.770956 1.351842,-2.274891 0.852691,-2.212892 z M 8.4769001,54.72853 c 1.6840469,-1.546034 6.5798229,0.09763 5.9374999,-0.6875 -1.89754,-1.615914 -3.316472,-3.755951 -3.96875,-6.15625 -0.6562499,2.28125 -1.3124999,4.5625 -1.9687499,6.84375 z"
style="color:#000000;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80.06713104px;line-height:125%;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Italic';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3817);fill-opacity:1;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
<metadata
id="metadata5540">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Apr 15 13:25:25 2013 -0400</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[vocx]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_ShapeString_tree.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson, [wandererfan]</dc:title>
</cc:Agent>
</dc:contributor>
<dc:subject>
<rdf:Bag>
<rdf:li>S</rdf:li>
<rdf:li>letter</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>A capital letter S, slightly italicized; color variation</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>445</width>
<height>488</height>
<height>511</height>
</rect>
</property>
<property name="sizePolicy">
@@ -54,7 +54,8 @@
<item row="5" column="0">
<widget class="QGroupBox" name="group_center">
<property name="toolTip">
<string>The coordinates of the point through which the axis of rotation passes.</string>
<string>The coordinates of the point through which the axis of rotation passes.
Change the direction of the axis itself in the property editor.</string>
</property>
<property name="title">
<string>Center of rotation</string>
@@ -127,7 +128,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="button_reset">
<property name="toolTip">
<string>Reset the coordinates of the center of rotation</string>
<string>Reset the coordinates of the center of rotation.</string>
</property>
<property name="text">
<string>Reset point</string>
@@ -142,7 +143,8 @@
<item>
<widget class="QCheckBox" name="checkbox_fuse">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be fused if they touch each other</string>
<string>If checked, the resulting objects in the array will be fused if they touch each other.
This only works if &quot;Link array&quot; is off.</string>
</property>
<property name="text">
<string>Fuse</string>
@@ -152,10 +154,14 @@
<item>
<widget class="QCheckBox" name="checkbox_link">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be Links instead of simple copies</string>
<string>If checked, the resulting object will be a &quot;Link array&quot; instead of a regular array.
A Link array is more efficient when creating multiple copies, but it cannot be fused together.</string>
</property>
<property name="text">
<string>Use Links</string>
<string>Link array</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@@ -166,7 +172,8 @@
<item row="1" column="0">
<widget class="QLabel" name="label_tan_distance">
<property name="toolTip">
<string>Distance from one element in the array to the next element in the same layer. It cannot be zero.</string>
<string>Distance from one element in one ring of the array to the next element in the same ring.
It cannot be zero.</string>
</property>
<property name="text">
<string>Tangential distance</string>
@@ -176,7 +183,8 @@
<item row="1" column="1">
<widget class="Gui::QuantitySpinBox" name="spinbox_tan_distance">
<property name="toolTip">
<string>Distance from one element in the array to the next element in the same layer. It cannot be zero.</string>
<string>Distance from one element in one ring of the array to the next element in the same ring.
It cannot be zero.</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
@@ -189,7 +197,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label_r_distance">
<property name="toolTip">
<string>Distance from the center of the array to the outer layers</string>
<string>Distance from one layer of objects to the next layer of objects.</string>
</property>
<property name="text">
<string>Radial distance</string>
@@ -199,7 +207,7 @@
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="spinbox_r_distance">
<property name="toolTip">
<string>Distance from the center of the array to the outer layers</string>
<string>Distance from one layer of objects to the next layer of objects.</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
@@ -212,7 +220,10 @@
<item row="3" column="1">
<widget class="QSpinBox" name="spinbox_symmetry">
<property name="toolTip">
<string>Number that controls how the objects will be distributed</string>
<string>The number of symmetry lines in the circular array.</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>1</number>
@@ -222,7 +233,11 @@
<item row="2" column="1">
<widget class="QSpinBox" name="spinbox_number">
<property name="toolTip">
<string>Number of circular arrays to create, including a copy of the original object. It must be at least 2.</string>
<string>Number of circular layers or rings to create, including a copy of the original object.
It must be at least 2.</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="value">
<number>3</number>
@@ -232,7 +247,8 @@
<item row="2" column="0">
<widget class="QLabel" name="label_number">
<property name="toolTip">
<string>Number of circular arrays to create, including a copy of the original object. It must be at least 2.</string>
<string>Number of circular layers or rings to create, including a copy of the original object.
It must be at least 2.</string>
</property>
<property name="text">
<string>Number of circular layers</string>
@@ -242,7 +258,7 @@
<item row="3" column="0">
<widget class="QLabel" name="label_symmetry">
<property name="toolTip">
<string>Number that controls how the objects will be distributed</string>
<string>The number of symmetry lines in the circular array.</string>
</property>
<property name="text">
<string>Symmetry</string>
@@ -41,10 +41,12 @@
<item row="6" column="0">
<widget class="QGroupBox" name="group_Z">
<property name="toolTip">
<string>Distance between the elements in the Z direction. Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions.</string>
<string>Distance between the elements in the Z direction.
Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions.
Negative values will result in copies produced in the negative direction.</string>
</property>
<property name="title">
<string>Interval Z</string>
<string>Z intervals</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
@@ -117,7 +119,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="button_reset_Z">
<property name="toolTip">
<string>Reset the distances</string>
<string>Reset the distances.</string>
</property>
<property name="text">
<string>Reset Z</string>
@@ -132,7 +134,8 @@
<item>
<widget class="QCheckBox" name="checkbox_fuse">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be fused if they touch each other</string>
<string>If checked, the resulting objects in the array will be fused if they touch each other.
This only works if &quot;Link array&quot; is off.</string>
</property>
<property name="text">
<string>Fuse</string>
@@ -142,10 +145,14 @@
<item>
<widget class="QCheckBox" name="checkbox_link">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be Links instead of simple copies</string>
<string>If checked, the resulting object will be a &quot;Link array&quot; instead of a regular array.
A Link array is more efficient when creating multiple copies, but it cannot be fused together.</string>
</property>
<property name="text">
<string>Use Links</string>
<string>Link array</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@@ -164,73 +171,6 @@
</property>
</spacer>
</item>
<item row="7" column="0">
<widget class="QGroupBox" name="group_copies">
<property name="toolTip">
<string>Number of elements in the array in the specified direction, including a copy of the original object. The number must be at least 1 in each direction.</string>
</property>
<property name="title">
<string>Number of elements</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<layout class="QGridLayout" name="grid_number">
<item row="0" column="0">
<widget class="QLabel" name="label_n_X">
<property name="text">
<string>X</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_n_Z">
<property name="text">
<string>Z</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_n_Y">
<property name="text">
<string>Y</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinbox_n_X">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinbox_n_Y">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinbox_n_Z">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_icon">
<property name="text">
@@ -241,10 +181,12 @@
<item row="4" column="0">
<widget class="QGroupBox" name="group_X">
<property name="toolTip">
<string>Distance between the elements in the X direction. Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions.</string>
<string>Distance between the elements in the X direction.
Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions.
Negative values will result in copies produced in the negative direction.</string>
</property>
<property name="title">
<string>Interval X</string>
<string>X intervals</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
@@ -317,7 +259,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="button_reset_X">
<property name="toolTip">
<string>Reset the distances</string>
<string>Reset the distances.</string>
</property>
<property name="text">
<string>Reset X</string>
@@ -330,10 +272,12 @@
<item row="5" column="0">
<widget class="QGroupBox" name="group_Y">
<property name="toolTip">
<string>Distance between the elements in the Y direction. Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions.</string>
<string>Distance between the elements in the Y direction.
Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions.
Negative values will result in copies produced in the negative direction.</string>
</property>
<property name="title">
<string>Interval Y</string>
<string>Y intervals</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
@@ -406,7 +350,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="button_reset_Y">
<property name="toolTip">
<string>Reset the distances</string>
<string>Reset the distances.</string>
</property>
<property name="text">
<string>Reset Y</string>
@@ -416,6 +360,74 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="group_copies">
<property name="toolTip">
<string>Number of elements in the array in the specified direction, including a copy of the original object.
The number must be at least 1 in each direction.</string>
</property>
<property name="title">
<string>Number of elements</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<layout class="QGridLayout" name="grid_number">
<item row="0" column="0">
<widget class="QLabel" name="label_n_X">
<property name="text">
<string>X</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_n_Z">
<property name="text">
<string>Z</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_n_Y">
<property name="text">
<string>Y</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinbox_n_X">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinbox_n_Y">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinbox_n_Z">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
@@ -429,10 +441,21 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>spinbox_n_X</tabstop>
<tabstop>spinbox_n_Y</tabstop>
<tabstop>spinbox_n_Z</tabstop>
<tabstop>input_X_x</tabstop>
<tabstop>input_X_y</tabstop>
<tabstop>input_X_z</tabstop>
<tabstop>button_reset_X</tabstop>
<tabstop>input_Y_x</tabstop>
<tabstop>input_Y_y</tabstop>
<tabstop>input_Y_z</tabstop>
<tabstop>button_reset_Y</tabstop>
<tabstop>input_Z_x</tabstop>
<tabstop>input_Z_y</tabstop>
<tabstop>input_Z_z</tabstop>
<tabstop>button_reset_Z</tabstop>
<tabstop>checkbox_fuse</tabstop>
<tabstop>checkbox_link</tabstop>
</tabstops>
@@ -54,7 +54,8 @@
<item row="5" column="0">
<widget class="QGroupBox" name="group_center">
<property name="toolTip">
<string>The coordinates of the point through which the axis of rotation passes.</string>
<string>The coordinates of the point through which the axis of rotation passes.
Change the direction of the axis itself in the property editor.</string>
</property>
<property name="title">
<string>Center of rotation</string>
@@ -127,7 +128,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="button_reset">
<property name="toolTip">
<string>Reset the coordinates of the center of rotation</string>
<string>Reset the coordinates of the center of rotation.</string>
</property>
<property name="text">
<string>Reset point</string>
@@ -142,7 +143,8 @@
<item>
<widget class="QCheckBox" name="checkbox_fuse">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be fused if they touch each other</string>
<string>If checked, the resulting objects in the array will be fused if they touch each other.
This only works if &quot;Link array&quot; is off.</string>
</property>
<property name="text">
<string>Fuse</string>
@@ -152,10 +154,14 @@
<item>
<widget class="QCheckBox" name="checkbox_link">
<property name="toolTip">
<string>If checked, the resulting objects in the array will be Links instead of simple copies</string>
<string>If checked, the resulting object will be a &quot;Link array&quot; instead of a regular array.
A Link array is more efficient when creating multiple copies, but it cannot be fused together.</string>
</property>
<property name="text">
<string>Use Links</string>
<string>Link array</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@@ -166,7 +172,9 @@
<item row="0" column="0">
<widget class="QLabel" name="label_angle">
<property name="toolTip">
<string>Sweeping angle of the polar distribution</string>
<string>Sweeping angle of the polar distribution.
A negative angle produces a polar pattern in the opposite direction.
The maximum absolute value is 360 degrees.</string>
</property>
<property name="text">
<string>Polar angle</string>
@@ -176,20 +184,29 @@
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="spinbox_angle">
<property name="toolTip">
<string>Sweeping angle of the polar distribution</string>
<string>Sweeping angle of the polar distribution.
A negative angle produces a polar pattern in the opposite direction.
The maximum absolute value is 360 degrees.</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>180.000000000000000</double>
<double>360.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_number">
<property name="toolTip">
<string>Number of elements in the array, including a copy of the original object. It must be at least 2.</string>
<string>Number of elements in the array, including a copy of the original object.
It must be at least 2.</string>
</property>
<property name="text">
<string>Number of elements</string>
@@ -199,10 +216,14 @@
<item row="1" column="1">
<widget class="QSpinBox" name="spinbox_number">
<property name="toolTip">
<string>Number of elements in the array, including a copy of the original object. It must be at least 2.</string>
<string>Number of elements in the array, including a copy of the original object.
It must be at least 2.</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="value">
<number>4</number>
<number>5</number>
</property>
</widget>
</item>
@@ -0,0 +1,447 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>694</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Style name</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboBoxStyles">
<property name="toolTip">
<string>The name of your style. Existing style names can be edited</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string/>
</property>
</item>
<item>
<property name="text">
<string>Add new...</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRename">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Renames the selected style</string>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonDelete">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Deletes the selected style</string>
</property>
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Text</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Font size</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Font name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Line spacing</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::QuantitySpinBox" name="FontSize">
<property name="toolTip">
<string>The size of the text in real-world units</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::QuantitySpinBox" name="LineSpacing">
<property name="toolTip">
<string>The spacing between lines of text in real-world units</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QFontComboBox" name="FontName">
<property name="toolTip">
<string>The font to use for texts and dimensions</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Units</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Scale multiplier</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Decimals</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Únit override</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Show unit</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="ScaleMultiplier">
<property name="toolTip">
<string>A multiplier value that affects distances shown by dimensions</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="UnitOverride">
<property name="toolTip">
<string>Forces dimensions to be shown in a specific unit</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="Decimals">
<property name="toolTip">
<string>The number of decimals to show on dimensions</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="ShowUnit">
<property name="toolTip">
<string>Shows the units suffix on dimensions or not</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Line and arrows</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Line width</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Extension overshoot</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Arrow size</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Show lines</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Dimension overshoot</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Extension lines</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Arrow type</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Line / text color</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ShowLines">
<property name="toolTip">
<string>Shows the dimension line or not</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="LineWidth">
<property name="toolTip">
<string>The width of the dimension lines</string>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::ColorButton" name="LineColor">
<property name="toolTip">
<string>The color of dimension lines, arrows and texts</string>
</property>
<property name="color">
<color>
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="ArrowType">
<property name="toolTip">
<string>The typeof arrows to use for dimensions</string>
</property>
<item>
<property name="text">
<string>Dot</string>
</property>
</item>
<item>
<property name="text">
<string>Arrow</string>
</property>
</item>
<item>
<property name="text">
<string>Tick</string>
</property>
</item>
</widget>
</item>
<item row="5" column="1">
<widget class="Gui::QuantitySpinBox" name="ArrowSize">
<property name="toolTip">
<string>The size of dimension arrows</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="Gui::QuantitySpinBox" name="DimensionOvershoot">
<property name="toolTip">
<string>How far must the main dimension line extend pass the measured points</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="Gui::QuantitySpinBox" name="ExtensionLines">
<property name="toolTip">
<string>The length of extension lines</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="Gui::QuantitySpinBox" name="ExtensionOvershoot">
<property name="toolTip">
<string>How far must the extension lines extend above the main dimension line</string>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
<customwidget>
<class>Gui::ColorButton</class>
<extends>QPushButton</extends>
<header>Gui/Widgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,975 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgSettingsDraft</class>
<widget class="QWidget" name="Gui::Dialog::DlgSettingsDraft">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>456</width>
<height>338</height>
</rect>
</property>
<property name="windowTitle">
<string>User interface settings</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>9</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>In-Command Shortcuts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
<item>
<layout class="QGridLayout" name="gridLayout_3">
<property name="topMargin">
<number>0</number>
</property>
<item row="4" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_8">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>`</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutCycleSnap</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_9">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>S</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutSnap</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Relative</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>R</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutRelative</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_4">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>O</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutClose</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_3">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>T</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutContinue</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Continue</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Increase Radius</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Cycle Snap</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_19">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>[</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutIncreaseRadius</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="5" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_17">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>]</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutDecreaseRadius</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Snap</string>
</property>
</widget>
</item>
<item row="5" column="4">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Decrease Radius</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Wipe</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_11">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>D</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutSubelementMode</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Add Hold</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_12">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>L</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutFill</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_5">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>P</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutCopy</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Fill</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Subelement Mode</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_13">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>E</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutSelectEdge</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_7">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>H</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutLength</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Select Edge</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_14">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>W</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutWipe</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_6">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>A</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutExit</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="2" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_15">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Q</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutAddHold</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Set WP</string>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_16">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>U</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutSetWP</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Restrict X</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="Gui::PrefLineEdit" name="lineEdit_10">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>X</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutRestrictX</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Restrict Y</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="Gui::PrefLineEdit" name="lineEdit_20">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Y</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>inCommandShortcutRestrictY</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="6" column="4">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Restrict Z</string>
</property>
</widget>
</item>
<item row="6" column="5">
<widget class="Gui::PrefLineEdit" name="lineEdit_18">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Z</string>
</property>
<property name="maxLength">
<number>1</number>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="clearButtonEnabled" stdset="0">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>RestrictZ</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Enable draft statusbar customization</string>
</property>
<property name="title">
<string>Draft Statusbar</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<string notr="true">DisplayStatusbar</string>
</property>
<property name="prefPath" stdset="0">
<string notr="true">Mod/Draft</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_4">
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="Gui::PrefCheckBox" name="gui::prefcheckbox_3">
<property name="toolTip">
<string>Enable snap statusbar widget</string>
</property>
<property name="text">
<string>Draft snap widget</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>DisplayStatusbarSnapWidget</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefCheckBox" name="gui::prefcheckbox_13">
<property name="toolTip">
<string>Enable draft statusbar annotation scale widget</string>
</property>
<property name="text">
<string>Annotation scale widget</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>DisplayStatusbarScaleWidget</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Draft</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
<customwidgets>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefLineEdit</class>
<extends>QLineEdit</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2020 Yorik van Havre <[email protected]> *
# * *
# * 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 *
# * *
# ***************************************************************************
"""
Provides all gui and tools to create and edit annotation styles
Provides Draft_AnnotationStyleEditor command
"""
import FreeCAD,FreeCADGui
import json
EMPTYSTYLE = {
"FontName":"Sans",
"FontSize":0,
"LineSpacing":0,
"ScaleMultiplier":1,
"ShowUnit":False,
"UnitOverride":"",
"Decimals":0,
"ShowLines":True,
"LineWidth":1,
"LineColor":255,
"ArrowType":0,
"ArrowSize":0,
"DimensionOvershoot":0,
"ExtensionLines":0,
"ExtensionOvershoot":0,
}
class Draft_AnnotationStyleEditor:
def __init__(self):
self.styles = {}
def GetResources(self):
return {'Pixmap' : ":icons/Draft_AnnotationStyleEditor.svg",
'MenuText': QT_TRANSLATE_NOOP("Draft_AnnotationStyleEditor", "Annotation styles..."),
'ToolTip' : QT_TRANSLATE_NOOP("Draft_AnnotationStyleEditor", "Manage or create annotation styles")}
def IsActive(self):
return bool(FreeCAD.ActiveDocument)
def Activated(self):
from PySide import QtGui
# load dialog
self.form = FreeCADGui.PySideUic.loadUi(":/ui/dialog_AnnotationStyleEditor.ui")
# center the dialog over FreeCAD window
mw = FreeCADGui.getMainWindow()
self.form.move(mw.frameGeometry().topLeft() + mw.rect().center() - self.form.rect().center())
# set icons
self.form.pushButtonDelete.setIcon(QtGui.QIcon(":/icons/edit_Cancel.svg"))
self.form.pushButtonRename.setIcon(QtGui.QIcon(":/icons/edit_Cancel.svg"))
# fill the styles combo
self.styles = self.read_meta()
for style in self.styles.keys():
self.form.comboBoxStyles.addItem(style)
# connect signals/slots
self.form.comboBoxStyles.currentIndexChanged.connect(self.on_style_changed)
self.form.pushButtonDelete.clicked.connect(self.on_delete)
self.form.pushButtonRename.clicked.connect(self.on_rename)
for attr in EMPTYSTYLE.keys():
control = getattr(self.form,attr)
for signal in ["textChanged","valueChanged","stateChanged"]:
if hasattr(control,signal):
getattr(control,signal).connect(self.update_style)
break
# show editor dialog
result = self.form.exec_()
# process if OK was clicked
if result:
self.save_meta(self.styles)
return
def read_meta(self):
"""reads the document Meta property and returns a dict"""
styles = {}
meta = FreeCAD.ActiveDocument.Meta
for key,value in meta.items():
if key.startswith("Draft_Style_"):
styles[key[12:]] = json.loads(value)
return styles
def save_meta(self,styles):
"""saves a dict to the document Meta property and updates objects"""
# save meta
changedstyles = []
meta = FreeCAD.ActiveDocument.Meta
for key,value in styles.items():
strvalue = json.dumps(value)
if meta["Draft_Style_"+key] and (meta["Draft_Style_"+key] != strvalue):
changedstyles.append(style)
meta["Draft_Style_"+key] = strvalue
FreeCAD.ActiveDocument.Meta = meta
# propagate changes to all annotations
for obj in self.get_annotations():
if obj.ViewObject.AnnotationStyle in styles.keys():
if obj.ViewObject.AnnotationStyle in changedstyles:
for attr,attrvalue in styles[obj.ViewObject.AnnotationStyle].items():
if hasattr(obj.ViewObject,attr):
setattr(obj.ViewObject,attr,attrvalue)
else:
obj.ViewObject.AnnotationStyle = " "
obj.ViewObject.AnnotationStyle == [" "] + styles.keys()
def on_style_changed(self,index):
"""called when the styles combobox is changed"""
from PySide import QtGui
if index <= 1:
# nothing happens
self.form.pushButtonDelete.setEnabled(False)
self.form.pushButtonRename.setEnabled(False)
self.fill_editor(None)
if index == 1:
# Add new... entry
reply = QtGui.QInputDialog.getText(None, "Create new style","Style name:")
if reply[1]:
# OK or Enter pressed
name = reply[0]
if name in self.styles:
reply = QtGui.QMessageBox.information(None,"Style exists","This style name already exists")
else:
# create new default style
self.styles[name] = EMPTYSTYLE
self.form.comboBoxStyles.addItem(name)
self.form.comboBoxStyles.setCurrentIndex(self.form.comboBoxStyles.count()-1)
elif index > 1:
# Existing style
self.form.pushButtonDelete.setEnabled(True)
self.form.pushButtonRename.setEnabled(True)
self.fill_editor(self.form.comboBoxStyles.itemText(index))
def on_delete(self):
"""called when the Delete button is pressed"""
from PySide import QtGui
index = self.form.comboBox.currentIndex()
style = self.form.comboBoxStyles.itemText(index)
if self.get_style_users(style):
reply = QtGui.QMessageBox.question(None, "Style in use", "This style is used by some objects in this document. Are you sure?",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.No:
return
self.form.comboBoxStyles.removeItem(index)
del self.styles[style]
def on_rename(self):
"""called when the Rename button is pressed"""
from PySide import QtGui
index = self.form.comboBox.currentIndex()
style = self.form.comboBoxStyles.itemText(index)
reply = QtGui.QInputDialog.getText(None, "Rename style","New name:",QtGui.QLineEdit.Normal,style)
if reply[1]:
# OK or Enter pressed
newname = reply[0]
self.form.comboBoxStyles.setItemText(index,newname)
value = self.styles[style]
del self.styles[style]
self.styles[newname] = value
def fill_editor(self,style):
"""fills the editor fields with the contents of a style"""
if style is None:
style = EMPTYSTYLE
for key,value in style.items():
setattr(self.form,key,value)
def update_style(self,arg=None):
"""updates the current style with the values from the editor"""
index = self.form.comboBox.currentIndex()
if index > 1:
values = {}
style = self.form.comboBoxStyles.itemText(index)
for key in EMPTYSTYLE.keys():
control = getattr(self.form,key)
for attr in ["text","value","state"]:
if hasattr(control,attr):
values[key] = getattr(control,attr)
self.styles[style] = values
def get_annotations(self):
"""gets all the objects that support annotation styles"""
users = []
for obj in FreeCAD.ActiveDocument.Objects:
vobj = obj.ViewObject
if hasattr(vobj,"AnnotationStyle"):
users.append(obj)
return users
def get_style_users(self,style):
"""get all objects using a certain style"""
users = []
for obj in self.get_annotations():
if obj.ViewObject.AnnotationStyle == style:
users.append(obj)
return users
FreeCADGui.addCommand('Draft_AnnotationStyleEditor', Draft_AnnotationStyleEditor())
+156
View File
@@ -0,0 +1,156 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides tools for creating arcs with the Draft Workbench."""
## @package gui_arcs
# \ingroup DRAFT
# \brief Provides tools for creating arcs with the Draft Workbench.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
import draftobjects.arc_3points as arc3
import draftguitools.gui_base as gui_base
import draftguitools.gui_trackers as trackers
import draftutils.utils as utils
from draftutils.translate import _tr
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class Arc_3Points(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Arc_3Points tool."""
def __init__(self):
super().__init__(name=_tr("Arc by 3 points"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Arc by 3 points"
_tip = ("Creates a circular arc by picking 3 points.\n"
"CTRL to snap, SHIFT to constrain.")
d = {'Pixmap': "Draft_Arc_3Points",
'MenuText': QT_TRANSLATE_NOOP("Draft_Arc_3Points", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Arc_3Points", _tip),
'Accel': 'A,T'}
return d
def Activated(self):
"""Execute when the command is called."""
super().Activated()
# Reset the values
self.points = []
self.normal = None
self.tracker = trackers.arcTracker()
self.tracker.autoinvert = False
# Set up the working plane and launch the Snapper
# with the indicated callbacks: one for when the user clicks
# on the 3D view, and another for when the user moves the pointer.
if hasattr(App, "DraftWorkingPlane"):
App.DraftWorkingPlane.setup()
Gui.Snapper.getPoint(callback=self.getPoint,
movecallback=self.drawArc)
def getPoint(self, point, info):
"""Get the point by clicking on the 3D view.
Every time the user clicks on the 3D view this method is run.
In this case, a point is appended to the list of points,
and the tracker is updated.
The object is finally created when three points are picked.
Parameters
----------
point: Base::Vector
The point selected in the 3D view.
info: str
Some information obtained about the point passed by the Snapper.
"""
# If there is not point, the command was cancelled
# so the command exits.
if not point:
self.tracker.off()
return
# Avoid adding the same point twice
if point not in self.points:
self.points.append(point)
if len(self.points) < 3:
# If one or two points were picked, set up again the Snapper
# to get further points, but update the `last` property
# with the last selected point.
#
# When two points are selected then we can turn on
# the arc tracker to show the preview of the final curve.
if len(self.points) == 2:
self.tracker.on()
Gui.Snapper.getPoint(last=self.points[-1],
callback=self.getPoint,
movecallback=self.drawArc)
else:
# If three points were already picked in the 3D view
# proceed with creating the final object.
# Draw a simple `Part::Feature` if the parameter is `True`.
if utils.get_param("UsePartPrimitives", False):
arc3.make_arc_3points([self.points[0],
self.points[1],
self.points[2]], primitive=True)
else:
arc3.make_arc_3points([self.points[0],
self.points[1],
self.points[2]], primitive=False)
self.tracker.off()
self.doc.recompute()
def drawArc(self, point, info):
"""Draw preview arc when we move the pointer in the 3D view.
It uses the `gui_trackers.arcTracker.setBy3Points` method.
Parameters
----------
point: Base::Vector
The dynamic point passed by the callback
as we move the pointer in the 3D view.
info: str
Some information obtained from the point by the Snapper.
"""
if len(self.points) == 2:
if point.sub(self.points[1]).Length > 0.001:
self.tracker.setBy3Points(self.points[0],
self.points[1],
point)
Draft_Arc_3Points = Arc_3Points
Gui.addCommand('Draft_Arc_3Points', Arc_3Points())
+20 -8
View File
@@ -28,6 +28,16 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
import draftguitools.gui_circulararray
import draftguitools.gui_polararray
import draftguitools.gui_orthoarray
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
True if draftguitools.gui_circulararray.__name__ else False
True if draftguitools.gui_polararray.__name__ else False
True if draftguitools.gui_orthoarray.__name__ else False
class ArrayGroupCommand:
@@ -35,22 +45,24 @@ class ArrayGroupCommand:
def GetCommands(self):
"""Tuple of array commands."""
return tuple(["Draft_OrthoArray",
"Draft_PolarArray", "Draft_CircularArray",
"Draft_PathArray", "Draft_PathLinkArray",
"Draft_PointArray"])
return ("Draft_OrthoArray",
"Draft_PolarArray", "Draft_CircularArray",
"Draft_PathArray", "Draft_PathLinkArray",
"Draft_PointArray")
def GetResources(self):
"""Add menu and tooltip."""
"""Set icon, menu and tooltip."""
_tooltip = ("Create various types of arrays, "
"including rectangular, polar, circular, "
"path, and point")
return {'MenuText': QT_TRANSLATE_NOOP("Draft", "Array tools"),
'ToolTip': QT_TRANSLATE_NOOP("Arch", _tooltip)}
return {'Pixmap': 'Draft_Array',
'MenuText': QT_TRANSLATE_NOOP("Draft", "Array tools"),
'ToolTip': QT_TRANSLATE_NOOP("Draft", _tooltip)}
def IsActive(self):
"""Return True when this command should be available."""
if App.ActiveDocument:
if App.activeDocument():
return True
else:
return False
+95 -1
View File
@@ -30,9 +30,103 @@
import FreeCAD as App
import FreeCADGui as Gui
import draftutils.todo as todo
from draftutils.messages import _msg, _log
class GuiCommandBase:
class GuiCommandSimplest(object):
"""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(object):
"""Generic class that is the basis of all Gui commands.
This class should eventually replace `DraftTools.DraftTool`,
@@ -20,7 +20,7 @@
# * USA *
# * *
# ***************************************************************************
"""Provides the Draft CircularArray tool."""
"""Provides the Draft CircularArray GuiCommand."""
## @package gui_circulararray
# \ingroup DRAFT
# \brief This module provides the Draft CircularArray tool.
@@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft
import Draft_rc
import Draft_rc # include resources, icons, ui files
from draftutils.messages import _msg, _log
from draftutils.translate import _tr
from draftguitools import gui_base
from drafttaskpanels import task_circulararray
import draftutils.todo as todo
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
bool(Draft_rc.__name__)
class GuiCommandCircularArray(gui_base.GuiCommandBase):
@@ -45,7 +47,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase):
def __init__(self):
super().__init__()
self.command_name = "CircularArray"
self.command_name = "Circular array"
self.location = None
self.mouse_event = None
self.view = None
@@ -56,14 +58,15 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase):
def GetResources(self):
"""Set icon, menu and tooltip."""
_msg = ("Creates copies of a selected object, "
_tip = ("Creates copies of a selected object, "
"and places the copies in a circular pattern.\n"
"The properties of the array can be further modified after "
"the new object is created, including turning it into "
"a different type of array.")
d = {'Pixmap': 'Draft_CircularArray',
'MenuText': QT_TRANSLATE_NOOP("Draft", "Circular array"),
'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)}
'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)}
return d
def Activated(self):
@@ -72,6 +75,10 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase):
We add callbacks that connect the 3D view with
the widgets of the task panel.
"""
_log("GuiCommand: {}".format(_tr(self.command_name)))
_msg("{}".format(16*"-"))
_msg("GuiCommand: {}".format(_tr(self.command_name)))
self.location = coin.SoLocation2Event.getClassTypeId()
self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId()
self.view = Draft.get3DView()
@@ -0,0 +1,82 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides tools to modify Draft dimensions.
For example, a tool to flip the direction of the text in the dimension
as the normal is sometimes not correctly calculated automatically.
"""
## @package gui_dimension_ops
# \ingroup DRAFT
# \brief Provides tools to modify Draft dimensions.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
import draftutils.utils as utils
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr
class FlipDimension(gui_base.GuiCommandNeedsSelection):
"""The Draft FlipDimension command definition.
Flip the normal direction of the selected dimensions.
It inherits `GuiCommandNeedsSelection` to set up the document
and other behavior. See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Flip dimension"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = ("Flip the normal direction of the selected dimensions "
"(linear, radial, angular).\n"
"If other objects are selected they are ignored.")
return {'Pixmap': 'Draft_FlipDimension',
'MenuText': QT_TRANSLATE_NOOP("Draft_FlipDimension",
"Flip dimension"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_FlipDimension",
_tip)}
def Activated(self):
"""Execute when the command is called."""
super().Activated()
for o in Gui.Selection.getSelection():
if utils.get_type(o) in ("Dimension", "AngularDimension"):
self.doc.openTransaction("Flip dimension")
_cmd = "App.activeDocument()." + o.Name + ".Normal"
_cmd += " = "
_cmd += "App.activeDocument()." + o.Name + ".Normal.negative()"
Gui.doCommand(_cmd)
self.doc.commitTransaction()
self.doc.recompute()
Draft_FlipDimension = FlipDimension
Gui.addCommand('Draft_FlipDimension', FlipDimension())
+79
View File
@@ -0,0 +1,79 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provide the Draft_ToggleGrid command to show the Draft grid."""
## @package gui_grid
# \ingroup DRAFT
# \brief Provide the Draft_ToggleGrid command to show the Draft grid.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr
class ToggleGrid(gui_base.GuiCommandSimplest):
"""The Draft ToggleGrid command definition.
If the grid tracker is invisible (hidden), it makes it visible (shown);
and if it is visible, it hides it.
It inherits `GuiCommandSimplest` to set up the document
and other behavior. See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Toggle grid"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Toggles the Draft grid on and off."
d = {'Pixmap': 'Draft_Grid',
'Accel': "G,R",
'MenuText': QT_TRANSLATE_NOOP("Draft_ToggleGrid",
"Toggle grid"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_ToggleGrid",
_tip),
'CmdType': 'ForEdit'}
return d
def Activated(self):
"""Execute when the command is called."""
super().Activated()
if hasattr(Gui, "Snapper"):
Gui.Snapper.setTrackers()
if Gui.Snapper.grid:
if Gui.Snapper.grid.Visible:
Gui.Snapper.grid.off()
Gui.Snapper.forceGridOff = True
else:
Gui.Snapper.grid.on()
Gui.Snapper.forceGridOff = False
Gui.addCommand('Draft_ToggleGrid', ToggleGrid())
+396
View File
@@ -0,0 +1,396 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides tools to do various operations with groups.
For example, add objects to groups, select objects inside groups,
set the automatic group in which to create objects, and add objects
to the construction group.
"""
## @package gui_groups
# \ingroup DRAFT
# \brief Provides tools to do various operations with groups.
import PySide.QtCore as QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
import draftutils.utils as utils
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr, translate
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class AddToGroup(gui_base.GuiCommandNeedsSelection):
"""GuiCommand for the Draft_AddToGroup tool.
It adds selected objects to a group, or removes them from any group.
It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Add to group"))
self.ungroup = QT_TRANSLATE_NOOP("Draft_AddToGroup",
"Ungroup")
def GetResources(self):
"""Set icon, menu and tooltip."""
_tooltip = ("Moves the selected objects to an existing group, "
"or removes them from any group.\n"
"Create a group first to use this tool.")
d = {'Pixmap': 'Draft_AddToGroup',
'MenuText': QT_TRANSLATE_NOOP("Draft_AddToGroup",
"Move to group"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_AddToGroup",
_tooltip)}
return d
def Activated(self):
"""Execute when the command is called."""
super().Activated()
self.groups = [self.ungroup]
self.groups.extend(utils.get_group_names())
self.labels = [self.ungroup]
for group in self.groups:
obj = self.doc.getObject(group)
if obj:
self.labels.append(obj.Label)
# It uses the `DraftToolBar` class defined in the `DraftGui` module
# and globally initialized in the `Gui` namespace,
# in order to pop up a menu with group labels
# or the default `Ungroup` text.
# Once the desired option is chosen
# it launches the `proceed` method.
self.ui = Gui.draftToolBar
self.ui.sourceCmd = self
self.ui.popupMenu(self.labels)
def proceed(self, labelname):
"""Place the selected objects in the chosen group or ungroup them.
Parameters
----------
labelname: str
The passed string with the name of the group.
It puts the selected objects inside this group.
"""
# Deactivate the source command of the `DraftToolBar` class
# so that it doesn't do more with this command.
self.ui.sourceCmd = None
# If the selected group matches the ungroup label,
# remove the selection from all groups.
if labelname == self.ungroup:
for obj in Gui.Selection.getSelection():
try:
utils.ungroup(obj)
except Exception:
pass
else:
# Otherwise try to add all selected objects to the chosen group
if labelname in self.labels:
i = self.labels.index(labelname)
g = self.doc.getObject(self.groups[i])
for obj in Gui.Selection.getSelection():
try:
g.addObject(obj)
except Exception:
pass
Gui.addCommand('Draft_AddToGroup', AddToGroup())
class SelectGroup(gui_base.GuiCommandNeedsSelection):
"""GuiCommand for the Draft_SelectGroup tool.
If the selection is a group, it selects all objects
with the same "parents" as this object. This means all objects
that are inside this group, including those in nested sub-groups.
If the selection is a simple object inside a group,
it will select the "brother" objects, that is, those objects that are
at the same level as this object, including the upper group
that contains them all.
NOTE: the second functionality is a bit strange, as it produces results
that are not very intuitive. Maybe we should change it and restrict
this command to only groups (`App::DocumentObjectGroup`) because
in this case it works in an intuitive manner, selecting
only the objects under the group.
It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Select group"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tooltip = ("If the selection is a group, it selects all objects "
"that are inside this group, including those in "
"nested sub-groups.\n"
"\n"
"If the selection is a simple object inside a group, "
'it will select the "brother" objects, that is,\n'
"those that are at the same level as this object, "
"including the upper group that contains them all.")
d = {'Pixmap': 'Draft_SelectGroup',
'MenuText': QT_TRANSLATE_NOOP("Draft_SelectGroup",
"Select group"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_SelectGroup",
_tooltip)}
return d
def Activated(self):
"""Execute when the command is called.
If the selection is a single group, it selects all objects
inside this group.
In other cases it selects all objects (children)
in the OutList of this object, and also all objects (parents)
in the InList of this object.
For all parents, it also selects the children of these.
"""
super().Activated()
sel = Gui.Selection.getSelection()
if len(sel) == 1:
if sel[0].isDerivedFrom("App::DocumentObjectGroup"):
cts = utils.get_group_contents(Gui.Selection.getSelection())
for o in cts:
Gui.Selection.addSelection(o)
return
for obj in sel:
# This selects the objects in the `OutList`
# which are actually `parents` but appear below in the tree.
# Regular objects usually have an empty `OutList`
# so this is skipped.
# But for groups, it selects the objects
# that it contains under it.
for child in obj.OutList:
Gui.Selection.addSelection(child)
# This selects the upper group that contains `obj`.
# Then for this group, it selects the objects in its `OutList`,
# which are at the same level as `obj` (brothers).
for parent in obj.InList:
Gui.Selection.addSelection(parent)
for child in parent.OutList:
Gui.Selection.addSelection(child)
# -------------------------------------------------------------------
# NOTE: the terminology here may be confusing.
# Those in the `InList` are actually `children` (dependents)
# but appear above in the tree view,
# and this is the reason they are called `parents`.
#
# Those in the `OutList` are actually `parents` (suppliers)
# but appear below in the tree, and this is the reason
# they are called `children`.
#
# InList
# |
# - object
# |
# - OutList
#
# -------------------------------------------------------------------
Gui.addCommand('Draft_SelectGroup', SelectGroup())
class SetAutoGroup(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_AutoGroup tool."""
def __init__(self):
super().__init__(name=_tr("Autogroup"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Select a group to add all Draft and Arch objects to."
return {'Pixmap': 'Draft_AutoGroup',
'MenuText': QT_TRANSLATE_NOOP("Draft_AutoGroup", "Autogroup"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_AutoGroup", _tip)}
def Activated(self):
"""Execute when the command is called.
It calls the `setAutogroup` method of the `DraftToolBar` class
installed inside the global `Gui` namespace.
"""
super().Activated()
if not hasattr(Gui, "draftToolBar"):
return
# It uses the `DraftToolBar` class defined in the `DraftGui` module
# and globally initialized in the `Gui` namespace to run
# some actions.
# If there is only a group selected, it runs the `AutoGroup` method.
self.ui = Gui.draftToolBar
s = Gui.Selection.getSelection()
if len(s) == 1:
if (utils.get_type(s[0]) == "Layer") or \
- (App.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups", False)
and (s[0].isDerivedFrom("App::DocumentObjectGroup")
or utils.get_type(s[0]) in ["Site", "Building",
"Floor", "BuildingPart"])):
self.ui.setAutoGroup(s[0].Name)
return
# Otherwise it builds a list of layers, with names and icons,
# including the options "None" and "Add new layer".
self.groups = ["None"]
gn = [o.Name for o in self.doc.Objects if utils.get_type(o) == "Layer"]
if App.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups", False):
gn.extend(utils.get_group_names())
if gn:
self.groups.extend(gn)
self.labels = [translate("draft", "None")]
self.icons = [self.ui.getIcon(":/icons/button_invalid.svg")]
for g in gn:
o = self.doc.getObject(g)
if o:
self.labels.append(o.Label)
self.icons.append(o.ViewObject.Icon)
self.labels.append(translate("draft", "Add new Layer"))
self.icons.append(self.ui.getIcon(":/icons/document-new.svg"))
# With the lists created is uses the interface
# to pop up a menu with layer options.
# Once the desired option is chosen
# it launches the `proceed` method.
self.ui.sourceCmd = self
pos = self.ui.autoGroupButton.mapToGlobal(QtCore.QPoint(0, self.ui.autoGroupButton.geometry().height()))
self.ui.popupMenu(self.labels, self.icons, pos)
def proceed(self, labelname):
"""Set the defined autogroup, or create a new layer.
Parameters
----------
labelname: str
The passed string with the name of the group or layer.
"""
# Deactivate the source command of the `DraftToolBar` class
# so that it doesn't do more with this command
# when it finishes.
self.ui.sourceCmd = None
if labelname in self.labels:
if labelname == self.labels[0]:
# First option "None" deactivates autogrouping
self.ui.setAutoGroup(None)
elif labelname == self.labels[-1]:
# Last option "Add new layer" creates new layer
Gui.runCommand("Draft_Layer")
else:
# Set autogroup to the chosen layer
i = self.labels.index(labelname)
self.ui.setAutoGroup(self.groups[i])
Gui.addCommand('Draft_AutoGroup', SetAutoGroup())
class AddToConstruction(gui_base.GuiCommandSimplest):
"""Gui Command for the AddToConstruction tool.
It adds the selected objects to the construction group
defined in the `DraftToolBar` class which is initialized
in the `Gui` namespace when the workbench loads.
It adds a construction group if it doesn't exist.
Added objects are also given the visual properties of the construction
group.
"""
def __init__(self):
super().__init__(name=_tr("Add to construction group"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Add to Construction group"
_tip = ("Adds the selected objects to the construction group,\n"
"and changes their appearance to the construction style.\n"
"It creates a construction group if it doesn't exist.")
d = {'Pixmap': 'Draft_AddConstruction',
'MenuText': QT_TRANSLATE_NOOP("Draft_AddConstruction", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_AddConstruction", _tip)}
return d
def Activated(self):
"""Execute when the command is called."""
super().Activated()
if not hasattr(Gui, "draftToolBar"):
return
col = Gui.draftToolBar.getDefaultColor("constr")
col = (float(col[0]), float(col[1]), float(col[2]), 0.0)
# Get the construction group or create it if it doesn't exist
gname = utils.get_param("constructiongroupname", "Construction")
grp = self.doc.getObject(gname)
if not grp:
grp = self.doc.addObject("App::DocumentObjectGroup", gname)
for obj in Gui.Selection.getSelection():
grp.addObject(obj)
# Change the appearance to the construction colors
vobj = obj.ViewObject
if "TextColor" in vobj.PropertiesList:
vobj.TextColor = col
if "PointColor" in vobj.PropertiesList:
vobj.PointColor = col
if "LineColor" in vobj.PropertiesList:
vobj.LineColor = col
if "ShapeColor" in vobj.PropertiesList:
vobj.ShapeColor = col
if hasattr(vobj, "Transparency"):
vobj.Transparency = 80
Draft_AddConstruction = AddToConstruction
Gui.addCommand('Draft_AddConstruction', AddToConstruction())
+76
View File
@@ -0,0 +1,76 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides the Draft_Heal command to heal older Draft files."""
## @package gui_health
# \ingroup DRAFT
# \brief Provides the Draft_Heal command to heal older Draft files.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
import Draft
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr
class Heal(gui_base.GuiCommandSimplest):
"""The Draft Heal command definition.
Heal faulty Draft objects saved with an earlier version of the program.
It inherits `GuiCommandSimplest` to set up the document
and other behavior. See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Heal"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = ("Heal faulty Draft objects saved with an earlier version "
"of the program.\n"
"If an object is selected it will try to heal that object "
"in particular,\n"
"otherwise it will try to heal all objects "
"in the active document.")
return {'Pixmap': 'Draft_Heal',
'MenuText': QT_TRANSLATE_NOOP("Draft_Heal", "Heal"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Heal", _tip)}
def Activated(self):
"""Execute when the command is called."""
super().Activated()
s = Gui.Selection.getSelection()
self.doc.openTransaction("Heal")
if s:
Draft.heal(s)
else:
Draft.heal()
self.doc.commitTransaction()
Gui.addCommand('Draft_Heal', Heal())
@@ -0,0 +1,109 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides certain add and remove line operations of the Draft Workbench.
These GuiCommands aren't really used anymore, as the same actions
are implemented directly in the Draft_Edit command.
"""
## @package gui_line_add_delete
# \ingroup DRAFT
# \brief Provides certain add and remove line operations.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
import Draft_rc
import DraftTools
import draftutils.utils as utils
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class AddPoint(DraftTools.Modifier):
"""GuiCommand to add a point to a line being drawn."""
def __init__(self):
self.running = False
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Add point"
_tip = "Adds a point to an existing Wire or B-spline."
return {'Pixmap': 'Draft_AddPoint',
'MenuText': QT_TRANSLATE_NOOP("Draft_AddPoint", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_AddPoint", _tip)}
def IsActive(self):
"""Return True when there is selection and the command is active."""
if Gui.Selection.getSelection():
return True
else:
return False
def Activated(self):
"""Execute when the command is called."""
selection = Gui.Selection.getSelection()
if selection:
if (utils.get_type(selection[0]) in ['Wire', 'BSpline']):
Gui.runCommand("Draft_Edit")
Gui.draftToolBar.vertUi(True)
Gui.addCommand('Draft_AddPoint', AddPoint())
class DelPoint(DraftTools.Modifier):
"""GuiCommand to delete a point to a line being drawn."""
def __init__(self):
self.running = False
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Remove point"
_tip = "Removes a point from an existing Wire or B-spline."
return {'Pixmap': 'Draft_DelPoint',
'MenuText': QT_TRANSLATE_NOOP("Draft_DelPoint", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_DelPoint", _tip)}
def IsActive(self):
"""Return True when there is selection and the command is active."""
if Gui.Selection.getSelection():
return True
else:
return False
def Activated(self):
"""Execute when the command is called."""
selection = Gui.Selection.getSelection()
if selection:
if (utils.get_type(selection[0]) in ['Wire', 'BSpline']):
Gui.runCommand("Draft_Edit")
Gui.draftToolBar.vertUi(False)
Gui.addCommand('Draft_DelPoint', DelPoint())
+163
View File
@@ -0,0 +1,163 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides certain line operations of the Draft Workbench.
These GuiCommands aren't really used anymore, as the same actions
are called from the task panel interface by other methods.
"""
## @package gui_lineops
# \ingroup DRAFT
# \brief Provides certain line operations in the Draft Workbench.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
import draftguitools.gui_base as gui_base
from draftutils.messages import _msg
from draftutils.translate import _tr
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class LineAction(gui_base.GuiCommandSimplest):
"""Base class for Line context GuiCommands.
This is inherited by the other GuiCommand classes to run
a set of similar actions when editing a line, wire, spline,
or bezier curve.
It inherits `GuiCommandSimplest` to set up the document
and other behavior. See this class for more information.
"""
def Activated(self, action="None"):
"""Execute when the command is called.
Parameters
----------
action: str
Indicates the type of action to perform with the line object.
It can be `'finish'`, `'close'`, or `'undo'`.
"""
if hasattr(App, "activeDraftCommand"):
_command = App.activeDraftCommand
else:
_msg(_tr("No active command."))
return
if (_command is not None
and _command.featureName in ("Line", "Polyline",
"BSpline", "BezCurve",
"CubicBezCurve")):
if action == "finish":
_command.finish(False)
elif action == "close":
_command.finish(True)
elif action == "undo":
_command.undolast()
class FinishLine(LineAction):
"""GuiCommand to finish any running line drawing operation."""
def __init__(self):
super().__init__(name=_tr("Finish line"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Finishes a line without closing it."
d = {'Pixmap': 'Draft_Finish',
'MenuText': QT_TRANSLATE_NOOP("Draft_FinishLine", "Finish line"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_FinishLine", _tip),
'CmdType': 'ForEdit'}
return d
def Activated(self):
"""Execute when the command is called.
It calls the `finish(False)` method of the active Draft command.
"""
super().Activated(action="finish")
Gui.addCommand('Draft_FinishLine', FinishLine())
class CloseLine(LineAction):
"""GuiCommand to close the line being drawn and finish the operation."""
def __init__(self):
super().__init__(name=_tr("Close line"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Closes the line being drawn, and finishes the operation."
d = {'Pixmap': 'Draft_Lock',
'MenuText': QT_TRANSLATE_NOOP("Draft_CloseLine", "Close Line"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_CloseLine", _tip),
'CmdType': 'ForEdit'}
return d
def Activated(self):
"""Execute when the command is called.
It calls the `finish(True)` method of the active Draft command.
"""
super().Activated(action="close")
Gui.addCommand('Draft_CloseLine', CloseLine())
class UndoLine(LineAction):
"""GuiCommand to undo the last drawn segment of a line."""
def __init__(self):
super().__init__(name=_tr("Undo line"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Undoes the last drawn segment of the line being drawn."
d = {'Pixmap': 'Draft_Rotate',
'MenuText': QT_TRANSLATE_NOOP("Draft_UndoLine",
"Undo last segment"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_UndoLine", _tip),
'CmdType': 'ForEdit'}
return d
def Activated(self):
"""Execute when the command is called.
It calls the `undolast` method of the active Draft command.
"""
super().Activated(action="undo")
Gui.addCommand('Draft_UndoLine', UndoLine())
@@ -0,0 +1,156 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides tools to change the slope of a line over the working plane.
It currently only works for a line in the XY plane, it changes the height
of one of its points in the Z direction to create a sloped line.
"""
## @package gui_lineslope
# \ingroup DRAFT
# \brief Provides tools to change the slope of a line over the working plane.
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
import draftutils.utils as utils
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr, translate
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class LineSlope(gui_base.GuiCommandNeedsSelection):
"""Gui Command for the Line slope tool.
For a line in the XY plane, it changes the height of one of its points
to create a sloped line.
To Do
-----
Make it work also with lines lying on the YZ and XZ planes,
or in an arbitrary plane, for which the normal is known.
"""
def __init__(self):
super().__init__(name=_tr("Change slope"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Set slope"
_tip = ("Sets the slope of the selected line "
"by changing the value of the Z value of one of its points.\n"
"If a polyline is selected, it will apply the slope "
"transformation to each of its segments.\n\n"
"The slope will always change the Z value, therefore "
"this command only works well for\n"
"straight Draft lines that are drawn in the XY plane. "
"Selected objects that aren't single lines will be ignored.")
return {'Pixmap': 'Draft_Slope',
'MenuText': QT_TRANSLATE_NOOP("Draft_Slope", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Slope", _tip)}
def Activated(self):
"""Execute when the command is called."""
super().Activated()
# for obj in Gui.Selection.getSelection():
# if utils.get_type(obj) != "Wire":
# _msg(translate("draft",
# "This tool only works with "
# "Draft Lines and Wires"))
# return
# TODO: create a .ui file with QtCreator and import it here
# instead of creating the interface programmatically,
# see the `gui_othoarray` module for an example.
w = QtGui.QWidget()
w.setWindowTitle(translate("Draft", "Slope"))
layout = QtGui.QHBoxLayout(w)
label = QtGui.QLabel(w)
label.setText(translate("Draft", "Slope")+":")
layout.addWidget(label)
self.spinbox = QtGui.QDoubleSpinBox(w)
self.spinbox.setMinimum(-9999.99)
self.spinbox.setMaximum(9999.99)
self.spinbox.setSingleStep(0.01)
_tip = ("New slope of the selected lines.\n"
"This is the tangent of the horizontal angle:\n"
"0 = horizontal\n"
"1 = 45 deg up\n"
"-1 = 45deg down\n")
label.setToolTip(translate("Draft", _tip))
self.spinbox.setToolTip(translate("Draft", _tip))
layout.addWidget(self.spinbox)
# In order to display our interface inside the task panel
# we must contain our interface inside a parent widget.
# Then our interface must be installed in this parent widget
# inside the attribute called "form".
taskwidget = QtGui.QWidget()
taskwidget.form = w
# The "accept" attribute of the parent widget
# should also contain a reference to a function that will be called
# when we press the "OK" button.
# Then we must show the container widget.
taskwidget.accept = self.accept
Gui.Control.showDialog(taskwidget)
def accept(self):
"""Execute when clicking the OK button or pressing Enter key.
It changes the slope of the line that lies on the XY plane.
TODO: make it work also with lines lying on the YZ and XZ planes.
"""
if hasattr(self, "spinbox"):
pc = self.spinbox.value()
self.doc.openTransaction("Change slope")
for obj in Gui.Selection.getSelection():
if utils.get_type(obj) == "Wire":
if len(obj.Points) > 1:
lp = None
np = []
for p in obj.Points:
if not lp:
lp = p
else:
v = p.sub(lp)
z = pc * App.Vector(v.x, v.y, 0).Length
lp = App.Vector(p.x, p.y, lp.z + z)
np.append(lp)
obj.Points = np
self.doc.commitTransaction()
Gui.Control.closeDialog()
self.doc.recompute()
Draft_Slope = LineSlope
Gui.addCommand('Draft_Slope', LineSlope())
+12 -5
View File
@@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft
import Draft_rc
import Draft_rc # include resources, icons, ui files
from draftutils.messages import _msg, _log
from draftutils.translate import _tr
from draftguitools import gui_base
from drafttaskpanels import task_orthoarray
import draftutils.todo as todo
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
bool(Draft_rc.__name__)
class GuiCommandOrthoArray(gui_base.GuiCommandBase):
@@ -45,7 +47,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase):
def __init__(self):
super().__init__()
self.command_name = "OrthoArray"
self.command_name = "Orthogonal array"
# self.location = None
self.mouse_event = None
self.view = None
@@ -56,14 +58,15 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase):
def GetResources(self):
"""Set icon, menu and tooltip."""
_msg = ("Creates copies of a selected object, "
_tip = ("Creates copies of a selected object, "
"and places the copies in an orthogonal pattern.\n"
"The properties of the array can be further modified after "
"the new object is created, including turning it into "
"a different type of array.")
d = {'Pixmap': 'Draft_Array',
'MenuText': QT_TRANSLATE_NOOP("Draft", "Array"),
'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)}
'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)}
return d
def Activated(self):
@@ -72,6 +75,10 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase):
We add callbacks that connect the 3D view with
the widgets of the task panel.
"""
_log("GuiCommand: {}".format(_tr(self.command_name)))
_msg("{}".format(16*"-"))
_msg("GuiCommand: {}".format(_tr(self.command_name)))
# self.location = coin.SoLocation2Event.getClassTypeId()
self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId()
self.view = Draft.get3DView()
+13 -6
View File
@@ -20,7 +20,7 @@
# * USA *
# * *
# ***************************************************************************
"""Provides the Draft PolarArray tool."""
"""Provides the Draft PolarArray GuiCommand."""
## @package gui_polararray
# \ingroup DRAFT
# \brief This module provides the Draft PolarArray tool.
@@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import Draft
import Draft_rc
import Draft_rc # include resources, icons, ui files
from draftutils.messages import _msg, _log
from draftutils.translate import _tr
from draftguitools import gui_base
from drafttaskpanels import task_polararray
import draftutils.todo as todo
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
bool(Draft_rc.__name__)
class GuiCommandPolarArray(gui_base.GuiCommandBase):
@@ -45,7 +47,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase):
def __init__(self):
super().__init__()
self.command_name = "PolarArray"
self.command_name = "Polar array"
self.location = None
self.mouse_event = None
self.view = None
@@ -56,14 +58,15 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase):
def GetResources(self):
"""Set icon, menu and tooltip."""
_msg = ("Creates copies of a selected object, "
_tip = ("Creates copies of a selected object, "
"and places the copies in a polar pattern.\n"
"The properties of the array can be further modified after "
"the new object is created, including turning it into "
"a different type of array.")
d = {'Pixmap': 'Draft_PolarArray',
'MenuText': QT_TRANSLATE_NOOP("Draft", "Polar array"),
'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)}
'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)}
return d
def Activated(self):
@@ -72,6 +75,10 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase):
We add callbacks that connect the 3D view with
the widgets of the task panel.
"""
_log("GuiCommand: {}".format(_tr(self.command_name)))
_msg("{}".format(16*"-"))
_msg("GuiCommand: {}".format(_tr(self.command_name)))
self.location = coin.SoLocation2Event.getClassTypeId()
self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId()
self.view = Draft.get3DView()
File diff suppressed because it is too large Load Diff
+368 -122
View File
@@ -28,64 +28,154 @@
# \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
class Draft_Snap_Lock:
"""Command to activate or deactivate all snap commands."""
# 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.
Activate or deactivate all snap methods at once.
"""
def __init__(self):
super(Draft_Snap_Lock, self).__init__(name=_tr("Main toggle snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Toggle On/Off"
_menu = "Main snapping toggle On/Off"
_tip = ("Activates or deactivates "
"all snap tools at once")
"all snap methods at once.")
return {'Pixmap': 'Snap_Lock',
'Accel': "Shift+S",
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Lock", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Lock", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Midpoint:
"""Command to snap to the midpoint of an edge."""
class Draft_Snap_Midpoint(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Midpoint tool.
Set snapping to the midpoint of an edge.
"""
def __init__(self):
super(Draft_Snap_Midpoint, self).__init__(name=_tr("Midpoint snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Midpoint"
_tip = "Snaps to midpoints of edges"
_tip = "Set snapping to the midpoint of an edge."
return {'Pixmap': 'Snap_Midpoint',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Midpoint", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Midpoint", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Perpendicular:
"""Command to snap to perdendicular of an edge."""
class Draft_Snap_Perpendicular(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Perpendicular tool.
Set snapping to a direction that is perpendicular to an edge.
"""
def __init__(self):
super(Draft_Snap_Perpendicular, self).__init__(name=_tr("Perpendicular snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Perpendicular"
_tip = "Snaps to perpendicular points on edges"
_tip = "Set snapping to a direction that is perpendicular to an edge."
return {'Pixmap': 'Snap_Perpendicular',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Perpendicular",
_menu),
@@ -93,46 +183,64 @@ class Draft_Snap_Perpendicular:
_tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Grid:
"""Command to snap to the intersection of grid lines."""
class Draft_Snap_Grid(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Grid tool.
Set snapping to the intersection of grid lines.
"""
def __init__(self):
super(Draft_Snap_Grid, self).__init__(name=_tr("Grid snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Snaps to grid points"
_tip = "Set snapping to the intersection of grid lines."
return {'Pixmap': 'Snap_Grid',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Grid", "Grid"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Grid", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Intersection:
"""Command to snap to the intersection of two edges."""
class Draft_Snap_Intersection(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Intersection tool.
Set snapping to the intersection of edges.
"""
def __init__(self):
super(Draft_Snap_Intersection, self).__init__(name=_tr("Intersection snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Intersection"
_tip = "Snaps to edges intersections"
_tip = "Set snapping to the intersection of edges."
return {'Pixmap': 'Snap_Intersection',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Intersection",
_menu),
@@ -140,227 +248,333 @@ class Draft_Snap_Intersection:
_tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Parallel:
"""Command to snap to the parallel of an edge."""
class Draft_Snap_Parallel(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Parallel tool.
Set snapping to a direction that is parallel to an edge.
"""
def __init__(self):
super(Draft_Snap_Parallel, self).__init__(name=_tr("Parallel snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Parallel"
_tip = "Snaps to parallel directions of edges"
_tip = "Set snapping to a direction that is parallel to an edge."
return {'Pixmap': 'Snap_Parallel',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Parallel", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Parallel", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Endpoint:
"""Command to snap to an endpoint of an edge."""
class Draft_Snap_Endpoint(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Endpoint tool.
Set snapping to endpoints of an edge.
"""
def __init__(self):
super(Draft_Snap_Endpoint, self).__init__(name=_tr("Endpoint snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Endpoint"
_tip = "Snaps to endpoints of edges"
_tip = "Set snapping to endpoints of an edge."
return {'Pixmap': 'Snap_Endpoint',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Endpoint", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Endpoint", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Angle:
"""Command to snap to 90 degree angles."""
class Draft_Snap_Angle(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Angle tool.
Set snapping to points in a circular arc located at multiples
of 30 and 45 degree angles.
"""
def __init__(self):
super(Draft_Snap_Angle, self).__init__(name=_tr("Angle snap (30 and 45 degrees)"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Snaps to 45 and 90 degrees points on arcs and circles"
_menu = "Angles (30 and 45 degrees)"
_tip = ("Set snapping to points in a circular arc located "
"at multiples of 30 and 45 degree angles.")
return {'Pixmap': 'Snap_Angle',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Angle", "Angles"),
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Angle", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Angle", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Center:
"""Command to snap to the centers of arcs and circumferences."""
class Draft_Snap_Center(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Center tool.
Set snapping to the center of a circular arc.
"""
def __init__(self):
super(Draft_Snap_Center, self).__init__(name=_tr("Arc center snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Snaps to center of circles and arcs"
_tip = "Set snapping to the center of a circular arc."
return {'Pixmap': 'Snap_Center',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Center", "Center"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Center", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Extension:
"""Command to snap to the extension of an edge."""
class Draft_Snap_Extension(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Extension tool.
Set snapping to the extension of an edge.
"""
def __init__(self):
super(Draft_Snap_Extension, self).__init__(name=_tr("Edge extension snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Extension"
_tip = "Snaps to extension of edges"
_tip = "Set snapping to the extension of an edge."
return {'Pixmap': 'Snap_Extension',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Extension", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Extension", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Near:
"""Command to snap to the nearest point of an edge."""
class Draft_Snap_Near(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Near tool.
Set snapping to the nearest point of an edge.
"""
def __init__(self):
super(Draft_Snap_Near, self).__init__(name=_tr("Near snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Snaps to nearest point on edges"
_tip = "Set snapping to the nearest point of an edge."
return {'Pixmap': 'Snap_Near',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Near", "Nearest"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Near", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Ortho:
"""Command to snap to the orthogonal directions."""
class Draft_Snap_Ortho(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Ortho tool.
Set snapping to a direction that is a multiple of 45 degrees
from a point.
"""
def __init__(self):
super(Draft_Snap_Ortho, self).__init__(name=_tr("Orthogonal snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Snaps to orthogonal and 45 degrees directions"
_menu = "Orthogonal angles (45 degrees)"
_tip = ("Set snapping to a direction that is a multiple "
"of 45 degrees from a point.")
return {'Pixmap': 'Snap_Ortho',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Ortho", "Ortho"),
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Ortho", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Ortho", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Special:
"""Command to snap to the special point of an object."""
class Draft_Snap_Special(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Special tool.
Set snapping to the special points defined inside an object.
"""
def __init__(self):
super(Draft_Snap_Special, self).__init__(name=_tr("Special point snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Special"
_tip = "Snaps to special locations of objects"
_tip = "Set snapping to the special points defined inside an object."
return {'Pixmap': 'Snap_Special',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Special", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Special", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_Dimensions:
"""Command to temporary show dimensions when snapping."""
class Draft_Snap_Dimensions(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Dimensions tool.
Show temporary linear dimensions when editing an object
and using other snapping methods.
"""
def __init__(self):
super(Draft_Snap_Dimensions, self).__init__(name=_tr("Dimension display"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Dimensions"
_tip = "Shows temporary dimensions when snapping to Arch objects"
_menu = "Show dimensions"
_tip = ("Show temporary linear dimensions when editing an object "
"and using other snapping methods.")
return {'Pixmap': 'Snap_Dimensions',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_Dimensions", _menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_Snap_Dimensions", _tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class Draft_Snap_WorkingPlane:
"""Command to snap to a point in the current working plane."""
class Draft_Snap_WorkingPlane(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_WorkingPlane tool.
Restricts snapping to a point in the current working plane.
If you select a point outside the working plane, for example,
by using other snapping methods, it will snap to that point's
projection in the current working plane.
"""
def __init__(self):
super(Draft_Snap_WorkingPlane, self).__init__(name=_tr("Working plane snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Working plane"
_tip = "Restricts the snapped point to the current working plane"
_tip = ("Restricts snapping to a point in the current "
"working plane.\n"
"If you select a point outside the working plane, "
"for example, by using other snapping methods,\n"
"it will snap to that point's projection "
"in the current working plane.")
return {'Pixmap': 'Snap_WorkingPlane',
'MenuText': QT_TRANSLATE_NOOP("Draft_Snap_WorkingPlane",
_menu),
@@ -368,12 +582,44 @@ class Draft_Snap_WorkingPlane:
_tip)}
def Activated(self):
"""Execute this when the command is called."""
"""Execute when the command is called."""
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())
class ShowSnapBar(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_ShowSnapBar tool.
Show the snap toolbar if it is hidden.
"""
def __init__(self):
super(ShowSnapBar, self).__init__(name=_tr("Show snap toolbar"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_tip = "Show the snap toolbar if it is hidden."
return {'Pixmap': 'Draft_Snap',
'MenuText': QT_TRANSLATE_NOOP("Draft_ShowSnapBar",
"Show snap toolbar"),
'ToolTip': QT_TRANSLATE_NOOP("Draft_ShowSnapBar",
_tip)}
def Activated(self):
"""Execute when the command is called."""
super(ShowSnapBar, self).Activated()
if hasattr(Gui, "Snapper"):
Gui.Snapper.show()
Gui.addCommand('Draft_ShowSnapBar', ShowSnapBar())
@@ -0,0 +1,207 @@
# ***************************************************************************
# * (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * (c) 2009, 2010 Ken Cline <[email protected]> *
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides tools to control the mode of other tools in the Draft Workbench.
For example, a construction mode, a continue mode to repeat commands,
and to toggle the appearance of certain shapes to wireframe.
"""
## @package gui_togglemodes
# \ingroup DRAFT
# \brief Provides certain mode operations of the Draft Workbench.
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
import Draft_rc
import draftguitools.gui_base as gui_base
from draftutils.messages import _msg
from draftutils.translate import _tr
# The module is used to prevent complaints from code checkers (flake8)
True if Draft_rc.__name__ else False
class BaseMode(gui_base.GuiCommandSimplest):
"""Base class for mode context GuiCommands.
This is inherited by the other GuiCommand classes to run
a set of similar actions when changing modes.
It inherits `GuiCommandSimplest` to set up the document
and other behavior. See this class for more information.
"""
def Activated(self, mode="None"):
"""Execute when the command is called.
Parameters
----------
action: str
Indicates the type of mode to switch to.
It can be `'construction'` or `'continue'`.
"""
super().Activated()
if hasattr(Gui, "draftToolBar"):
_ui = Gui.draftToolBar
else:
_msg(_tr("No active Draft Toolbar."))
return
if _ui is not None:
if mode == "construction" and hasattr(_ui, "constrButton"):
_ui.constrButton.toggle()
elif mode == "continue":
_ui.toggleContinue()
class ToggleConstructionMode(BaseMode):
"""GuiCommand for the Draft_ToggleConstructionMode tool.
When construction mode is active, the following objects created
will be included in the construction group, and will be drawn
with the specified color and properties.
"""
def __init__(self):
super().__init__(name=_tr("Construction mode"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Toggle construction mode"
_tip = ("Toggles the Construction mode.\n"
"When this is active, the following objects created "
"will be included in the construction group, "
"and will be drawn with the specified color "
"and properties.")
d = {'Pixmap': 'Draft_Construction',
'MenuText': QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode",
_menu),
'Accel': "C, M",
'ToolTip': QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode",
_tip)}
return d
def Activated(self):
"""Execute when the command is called.
It calls the `toggle()` method of the construction button
in the `DraftToolbar` class.
"""
super().Activated(mode="construction")
Gui.addCommand('Draft_ToggleConstructionMode', ToggleConstructionMode())
class ToggleContinueMode(BaseMode):
"""GuiCommand for the Draft_ToggleContinueMode tool.
When continue mode is active, any drawing tool that is terminated
will automatically start again. This can be used to draw several
objects one after the other in succession.
"""
def __init__(self):
super().__init__(name=_tr("Continue mode"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Toggle continue mode"
_tip = ("Toggles the Continue mode.\n"
"When this is active, any drawing tool that is terminated "
"will automatically start again.\n"
"This can be used to draw several objects "
"one after the other in succession.")
d = {'Pixmap': 'Draft_Continue',
'MenuText': QT_TRANSLATE_NOOP("Draft_ToggleContinueMode",
_menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_ToggleContinueMode",
_tip)}
return d
def Activated(self):
"""Execute when the command is called.
It calls the `toggleContinue()` method of the `DraftToolbar` class.
"""
super().Activated(mode="continue")
Gui.addCommand('Draft_ToggleContinueMode', ToggleContinueMode())
class ToggleDisplayMode(gui_base.GuiCommandNeedsSelection):
"""GuiCommand for the Draft_ToggleDisplayMode tool.
Switches the display mode of selected objects from flatlines
to wireframe and back.
It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
def __init__(self):
super().__init__(name=_tr("Toggle display mode"))
def GetResources(self):
"""Set icon, menu and tooltip."""
_menu = "Toggle normal/wireframe display"
_tip = ("Switches the display mode of selected objects "
"from flatlines to wireframe and back.\n"
"This is helpful to quickly visualize objects "
"that are hidden by other objects.\n"
"This is intended to be used with closed shapes "
"and solids, and doesn't affect open wires.")
d = {'Pixmap': 'Draft_SwitchMode',
'Accel': "Shift+Space",
'MenuText': QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode",
_menu),
'ToolTip': QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode",
_tip)}
return d
def Activated(self):
"""Execute when the command is called.
It tests the view provider of the selected objects
and changes their `DisplayMode` from `'Wireframe'`
to `'Flat Lines'`, and the other way around, if possible.
"""
super().Activated()
for obj in Gui.Selection.getSelection():
if obj.ViewObject.DisplayMode == "Flat Lines":
if "Wireframe" in obj.ViewObject.listDisplayModes():
obj.ViewObject.DisplayMode = "Wireframe"
elif obj.ViewObject.DisplayMode == "Wireframe":
if "Flat Lines" in obj.ViewObject.listDisplayModes():
obj.ViewObject.DisplayMode = "Flat Lines"
Gui.addCommand('Draft_ToggleDisplayMode', ToggleDisplayMode())
+397
View File
@@ -0,0 +1,397 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the object code for Draft Dimension.
"""
## @package dimension
# \ingroup DRAFT
# \brief This module provides the object code for Draft Dimension.
import FreeCAD as App
import math
from PySide.QtCore import QT_TRANSLATE_NOOP
import DraftGeomUtils, DraftVecUtils
import draftutils.gui_utils as gui_utils
import draftutils.utils as utils
from draftobjects.draft_annotation import DraftAnnotation
if App.GuiUp:
from draftviewproviders.view_dimension import ViewProviderDimensionBase
from draftviewproviders.view_dimension import ViewProviderLinearDimension
from draftviewproviders.view_dimension import ViewProviderAngularDimension
def make_dimension(p1,p2,p3=None,p4=None):
"""makeDimension(p1,p2,[p3]) or makeDimension(object,i1,i2,p3)
or makeDimension(objlist,indices,p3): Creates a Dimension object with
the dimension line passign through p3.The current line width and color
will be used. There are multiple ways to create a dimension, depending on
the arguments you pass to it:
- (p1,p2,p3): creates a standard dimension from p1 to p2
- (object,i1,i2,p3): creates a linked dimension to the given object,
measuring the distance between its vertices indexed i1 and i2
- (object,i1,mode,p3): creates a linked dimension
to the given object, i1 is the index of the (curved) edge to measure,
and mode is either "radius" or "diameter".
"""
if not App.ActiveDocument:
App.Console.PrintError("No active document. Aborting\n")
return
obj = App.ActiveDocument.addObject("App::FeaturePython","Dimension")
LinearDimension(obj)
if App.GuiUp:
ViewProviderLinearDimension(obj.ViewObject)
if isinstance(p1,App.Vector) and isinstance(p2,App.Vector):
obj.Start = p1
obj.End = p2
if not p3:
p3 = p2.sub(p1)
p3.multiply(0.5)
p3 = p1.add(p3)
elif isinstance(p2,int) and isinstance(p3,int):
l = []
idx = (p2,p3)
l.append((p1,"Vertex"+str(p2+1)))
l.append((p1,"Vertex"+str(p3+1)))
obj.LinkedGeometry = l
obj.Support = p1
p3 = p4
if not p3:
v1 = obj.Base.Shape.Vertexes[idx[0]].Point
v2 = obj.Base.Shape.Vertexes[idx[1]].Point
p3 = v2.sub(v1)
p3.multiply(0.5)
p3 = v1.add(p3)
elif isinstance(p3,str):
l = []
l.append((p1,"Edge"+str(p2+1)))
if p3 == "radius":
#l.append((p1,"Center"))
if App.GuiUp:
obj.ViewObject.Override = "R $dim"
obj.Diameter = False
elif p3 == "diameter":
#l.append((p1,"Diameter"))
if App.GuiUp:
obj.ViewObject.Override = "Ø $dim"
obj.Diameter = True
obj.LinkedGeometry = l
obj.Support = p1
p3 = p4
if not p3:
p3 = p1.Shape.Edges[p2].Curve.Center.add(App.Vector(1,0,0))
obj.Dimline = p3
if hasattr(App,"DraftWorkingPlane"):
normal = App.DraftWorkingPlane.axis
else:
normal = App.Vector(0,0,1)
if App.GuiUp:
# invert the normal if we are viewing it from the back
vnorm = gui_utils.get3DView().getViewDirection()
if vnorm.getAngle(normal) < math.pi/2:
normal = normal.negative()
obj.Normal = normal
if App.GuiUp:
gui_utils.format_object(obj)
gui_utils.select(obj)
return obj
def make_angular_dimension(center,angles,p3,normal=None):
"""makeAngularDimension(center,angle1,angle2,p3,[normal]): creates an angular Dimension
from the given center, with the given list of angles, passing through p3.
"""
if not App.ActiveDocument:
App.Console.PrintError("No active document. Aborting\n")
return
obj = App.ActiveDocument.addObject("App::FeaturePython","Dimension")
AngularDimension(obj)
if App.GuiUp:
ViewProviderAngularDimension(obj.ViewObject)
obj.Center = center
for a in range(len(angles)):
if angles[a] > 2*math.pi:
angles[a] = angles[a]-(2*math.pi)
obj.FirstAngle = math.degrees(angles[1])
obj.LastAngle = math.degrees(angles[0])
obj.Dimline = p3
if not normal:
if hasattr(App,"DraftWorkingPlane"):
normal = App.DraftWorkingPlane.axis
else:
normal = App.Vector(0,0,1)
if App.GuiUp:
# invert the normal if we are viewing it from the back
vnorm = gui_utils.get3DView().getViewDirection()
if vnorm.getAngle(normal) < math.pi/2:
normal = normal.negative()
obj.Normal = normal
if App.GuiUp:
gui_utils.format_object(obj)
gui_utils.select(obj)
return obj
class DimensionBase(DraftAnnotation):
"""
The Draft Dimension Base object
This class is not used directly, but inherited by all dimension
objects.
"""
def __init__(self, obj, tp = "Dimension"):
"""Add common dimension properties to the object and set them"""
super(DimensionBase, self).__init__(obj, tp)
# Draft
obj.addProperty("App::PropertyVector",
"Normal",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The normal direction of this dimension"))
obj.addProperty("App::PropertyLink",
"Support",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The object measured by this dimension"))
obj.addProperty("App::PropertyLinkSubList",
"LinkedGeometry",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The geometry this dimension is linked to"))
obj.addProperty("App::PropertyVectorDistance",
"Dimline",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"Point on which the dimension \n"
"line is placed."))
obj.Dimline = App.Vector(0,1,0)
obj.Normal = App.Vector(0,0,1)
def execute(self, obj):
'''Do something when recompute object'''
return
def onChanged(self,obj,prop):
'''Do something when a property has changed'''
return
class LinearDimension(DimensionBase):
"""
The Draft Linear Dimension object
"""
def __init__(self, obj):
super(LinearDimension, self).__init__(obj, "LinearDimension")
obj.Proxy = self
self.init_properties(obj)
def init_properties(self, obj):
"""Add Linear Dimension specific properties to the object and set them"""
# Draft
obj.addProperty("App::PropertyVectorDistance",
"Start",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"Startpoint of dimension"))
obj.addProperty("App::PropertyVectorDistance",
"End",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"Endpoint of dimension"))
obj.addProperty("App::PropertyVector",
"Direction",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The normal direction of this dimension"))
obj.addProperty("App::PropertyLength",
"Distance",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The measurement of this dimension"))
obj.addProperty("App::PropertyBool",
"Diameter",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"For arc/circle measurements, false = radius, true = diameter"))
obj.Start = App.Vector(0,0,0)
obj.End = App.Vector(1,0,0)
def onChanged(self,obj,prop):
'''Do something when a property has changed'''
if hasattr(obj, "Distance"):
obj.setEditorMode('Distance', 1)
#if hasattr(obj,"Normal"):
# obj.setEditorMode('Normal', 2)
if hasattr(obj, "Support"):
obj.setEditorMode('Support', 2)
def execute(self, obj):
""" Set start point and end point according to the linked geometry"""
if obj.LinkedGeometry:
if len(obj.LinkedGeometry) == 1:
lobj = obj.LinkedGeometry[0][0]
lsub = obj.LinkedGeometry[0][1]
if len(lsub) == 1:
if "Edge" in lsub[0]:
n = int(lsub[0][4:])-1
edge = lobj.Shape.Edges[n]
if DraftGeomUtils.geomType(edge) == "Line":
obj.Start = edge.Vertexes[0].Point
obj.End = edge.Vertexes[-1].Point
elif DraftGeomUtils.geomType(edge) == "Circle":
c = edge.Curve.Center
r = edge.Curve.Radius
a = edge.Curve.Axis
ray = obj.Dimline.sub(c).projectToPlane(App.Vector(0,0,0),a)
if (ray.Length == 0):
ray = a.cross(App.Vector(1,0,0))
if (ray.Length == 0):
ray = a.cross(App.Vector(0,1,0))
ray = DraftVecUtils.scaleTo(ray,r)
if hasattr(obj,"Diameter"):
if obj.Diameter:
obj.Start = c.add(ray.negative())
obj.End = c.add(ray)
else:
obj.Start = c
obj.End = c.add(ray)
elif len(lsub) == 2:
if ("Vertex" in lsub[0]) and ("Vertex" in lsub[1]):
n1 = int(lsub[0][6:])-1
n2 = int(lsub[1][6:])-1
obj.Start = lobj.Shape.Vertexes[n1].Point
obj.End = lobj.Shape.Vertexes[n2].Point
elif len(obj.LinkedGeometry) == 2:
lobj1 = obj.LinkedGeometry[0][0]
lobj2 = obj.LinkedGeometry[1][0]
lsub1 = obj.LinkedGeometry[0][1]
lsub2 = obj.LinkedGeometry[1][1]
if (len(lsub1) == 1) and (len(lsub2) == 1):
if ("Vertex" in lsub1[0]) and ("Vertex" in lsub2[1]):
n1 = int(lsub1[0][6:])-1
n2 = int(lsub2[0][6:])-1
obj.Start = lobj1.Shape.Vertexes[n1].Point
obj.End = lobj2.Shape.Vertexes[n2].Point
# set the distance property
total_len = (obj.Start.sub(obj.End)).Length
if round(obj.Distance.Value, utils.precision()) != round(total_len, utils.precision()):
obj.Distance = total_len
if App.GuiUp:
if obj.ViewObject:
obj.ViewObject.update()
class AngularDimension(DimensionBase):
"""
The Draft AngularDimension object
"""
def __init__(self, obj):
super(AngularDimension, self).__init__(obj, "AngularDimension")
self.init_properties(obj)
obj.Proxy = self
def init_properties(self, obj):
"""Add Angular Dimension specific properties to the object and set them"""
obj.addProperty("App::PropertyAngle",
"FirstAngle",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"Start angle of the dimension"))
obj.addProperty("App::PropertyAngle",
"LastAngle",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"End angle of the dimension"))
obj.addProperty("App::PropertyVectorDistance",
"Center",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The center point of this dimension"))
obj.addProperty("App::PropertyAngle",
"Angle",
"Draft",
QT_TRANSLATE_NOOP("App::Property",
"The measurement of this dimension"))
obj.FirstAngle = 0
obj.LastAngle = 90
obj.Dimline = App.Vector(0,1,0)
obj.Center = App.Vector(0,0,0)
obj.Normal = App.Vector(0,0,1)
def execute(self, fp):
'''Do something when recompute object'''
if fp.ViewObject:
fp.ViewObject.update()
def onChanged(self,obj,prop):
'''Do something when a property has changed'''
super().onChanged(obj, prop)
if hasattr(obj,"Angle"):
obj.setEditorMode('Angle',1)
if hasattr(obj,"Normal"):
obj.setEditorMode('Normal',2)
if hasattr(obj,"Support"):
obj.setEditorMode('Support',2)
@@ -0,0 +1,64 @@
# ***************************************************************************
# * (c) 2020 Carlo Pavan *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the object code for Draft Annotation.
"""
## @package annotation
# \ingroup DRAFT
# \brief This module provides the object code for Draft Annotation.
import FreeCAD as App
from PySide.QtCore import QT_TRANSLATE_NOOP
from draftutils import gui_utils
class DraftAnnotation(object):
"""The Draft Annotation Base object
This class is not used directly, but inherited by all annotation
objects.
"""
def __init__(self, obj, tp="Annotation"):
"""Add general Annotation properties to the object"""
self.Type = tp
def __getstate__(self):
return self.Type
def __setstate__(self,state):
if state:
self.Type = state
def execute(self,obj):
'''Do something when recompute object'''
return
def onChanged(self, obj, prop):
'''Do something when a property has changed'''
return
+239
View File
@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the object code for Draft Label.
"""
## @package label
# \ingroup DRAFT
# \brief This module provides the object code for Draft Label.
import FreeCAD as App
import math
from PySide.QtCore import QT_TRANSLATE_NOOP
import DraftGeomUtils
import draftutils.gui_utils as gui_utils
import draftutils.utils as utils
from draftobjects.draft_annotation import DraftAnnotation
if App.GuiUp:
from draftviewproviders.view_label import ViewProviderLabel
def make_label(targetpoint=None, target=None, direction=None,
distance=None, labeltype=None, placement=None):
"""
make_label(targetpoint, target, direction, distance, labeltype, placement)
Function to create a Draft Label annotation object
Parameters
----------
targetpoint : App::Vector
To be completed
target : LinkSub
To be completed
direction : String
Straight direction of the label
["Horizontal","Vertical","Custom"]
distance : Quantity
Length of the straight segment of label leader line
labeltype : String
Label type in
["Custom","Name","Label","Position",
"Length","Area","Volume","Tag","Material"]
placement : Base::Placement
To be completed
Returns
-------
obj : App::DocumentObject
Newly created label object
"""
obj = App.ActiveDocument.addObject("App::FeaturePython",
"dLabel")
Label(obj)
if App.GuiUp:
ViewProviderLabel(obj.ViewObject)
if targetpoint:
obj.TargetPoint = targetpoint
if target:
obj.Target = target
if direction:
obj.StraightDirection = direction
if distance:
obj.StraightDistance = distance
if labeltype:
obj.LabelType = labeltype
if placement:
obj.Placement = placement
if App.GuiUp:
gui_utils.format_object(obj)
gui_utils.select(obj)
return obj
class Label(DraftAnnotation):
"""The Draft Label object"""
def __init__(self, obj):
super(Label, self).__init__(obj, "Label")
self.init_properties(obj)
obj.Proxy = self
def init_properties(self, obj):
"""Add properties to the object and set them"""
obj.addProperty("App::PropertyPlacement",
"Placement",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The placement of this object"))
obj.addProperty("App::PropertyDistance",
"StraightDistance",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The length of the straight segment"))
obj.addProperty("App::PropertyVector",
"TargetPoint",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The point indicated by this label"))
obj.addProperty("App::PropertyVectorList",
"Points",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The points defining the label polyline"))
obj.addProperty("App::PropertyEnumeration",
"StraightDirection",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The direction of the straight segment"))
obj.addProperty("App::PropertyEnumeration",
"LabelType",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The type of information shown by this label"))
obj.addProperty("App::PropertyLinkSub",
"Target",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The target object of this label"))
obj.addProperty("App::PropertyStringList",
"CustomText",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The text to display when type is set to custom"))
obj.addProperty("App::PropertyStringList",
"Text",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The text displayed by this label"))
obj.StraightDirection = ["Horizontal","Vertical","Custom"]
obj.LabelType = ["Custom","Name","Label","Position",
"Length","Area","Volume","Tag","Material"]
obj.setEditorMode("Text",1)
obj.StraightDistance = 1
obj.TargetPoint = App.Vector(2,-1,0)
obj.CustomText = "Label"
def execute(self,obj):
'''Do something when recompute object'''
if obj.StraightDirection != "Custom":
p1 = obj.Placement.Base
if obj.StraightDirection == "Horizontal":
p2 = App.Vector(obj.StraightDistance.Value,0,0)
else:
p2 = App.Vector(0,obj.StraightDistance.Value,0)
p2 = obj.Placement.multVec(p2)
# p3 = obj.Placement.multVec(obj.TargetPoint)
p3 = obj.TargetPoint
obj.Points = [p1,p2,p3]
if obj.LabelType == "Custom":
if obj.CustomText:
obj.Text = obj.CustomText
elif obj.Target and obj.Target[0]:
if obj.LabelType == "Name":
obj.Text = [obj.Target[0].Name]
elif obj.LabelType == "Label":
obj.Text = [obj.Target[0].Label]
elif obj.LabelType == "Tag":
if hasattr(obj.Target[0],"Tag"):
obj.Text = [obj.Target[0].Tag]
elif obj.LabelType == "Material":
if hasattr(obj.Target[0],"Material"):
if hasattr(obj.Target[0].Material,"Label"):
obj.Text = [obj.Target[0].Material.Label]
elif obj.LabelType == "Position":
p = obj.Target[0].Placement.Base
if obj.Target[1]:
if "Vertex" in obj.Target[1][0]:
p = obj.Target[0].Shape.Vertexes[int(obj.Target[1][0][6:])-1].Point
obj.Text = [App.Units.Quantity(x,App.Units.Length).UserString for x in tuple(p)]
elif obj.LabelType == "Length":
if hasattr(obj.Target[0],'Shape'):
if hasattr(obj.Target[0].Shape,"Length"):
obj.Text = [App.Units.Quantity(obj.Target[0].Shape.Length,App.Units.Length).UserString]
if obj.Target[1] and ("Edge" in obj.Target[1][0]):
obj.Text = [App.Units.Quantity(obj.Target[0].Shape.Edges[int(obj.Target[1][0][4:])-1].Length,App.Units.Length).UserString]
elif obj.LabelType == "Area":
if hasattr(obj.Target[0],'Shape'):
if hasattr(obj.Target[0].Shape,"Area"):
obj.Text = [App.Units.Quantity(obj.Target[0].Shape.Area,App.Units.Area).UserString.replace("^2","²")]
if obj.Target[1] and ("Face" in obj.Target[1][0]):
obj.Text = [App.Units.Quantity(obj.Target[0].Shape.Faces[int(obj.Target[1][0][4:])-1].Area,App.Units.Area).UserString]
elif obj.LabelType == "Volume":
if hasattr(obj.Target[0],'Shape'):
if hasattr(obj.Target[0].Shape,"Volume"):
obj.Text = [App.Units.Quantity(obj.Target[0].Shape.Volume,App.Units.Volume).UserString.replace("^3","³")]
def onChanged(self,obj,prop):
'''Do something when a property has changed'''
return
+131
View File
@@ -0,0 +1,131 @@
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the object code for Draft Text.
"""
## @package text
# \ingroup DRAFT
# \brief This module provides the object code for Draft Text.
import FreeCAD as App
import math
from PySide.QtCore import QT_TRANSLATE_NOOP
import DraftGeomUtils
import draftutils.gui_utils as gui_utils
import draftutils.utils as utils
from draftobjects.draft_annotation import DraftAnnotation
if App.GuiUp:
from draftviewproviders.view_text import ViewProviderText
def make_text(stringslist, point=App.Vector(0,0,0), screen=False):
"""makeText(strings, point, screen)
Creates a Text object containing the given strings.
The current color and text height and font
specified in preferences are used.
Parameters
----------
stringlist : List
Given list of strings, one string by line (strings can also
be one single string)
point : App::Vector
insert point of the text
screen : Bool
If screen is True, the text always faces the view direction.
"""
if not App.ActiveDocument:
App.Console.PrintError("No active document. Aborting\n")
return
utils.type_check([(point, App.Vector)], "makeText")
if not isinstance(stringslist,list): stringslist = [stringslist]
obj = App.ActiveDocument.addObject("App::FeaturePython","Text")
Text(obj)
obj.Text = stringslist
obj.Placement.Base = point
if App.GuiUp:
ViewProviderText(obj.ViewObject)
if screen:
obj.ViewObject.DisplayMode = "3D text"
h = utils.get_param("textheight",0.20)
if screen:
h = h*10
obj.ViewObject.FontSize = h
obj.ViewObject.FontName = utils.get_param("textfont","")
obj.ViewObject.LineSpacing = 1
gui_utils.format_object(obj)
gui_utils.select(obj)
return obj
class Text(DraftAnnotation):
"""The Draft Text object"""
def __init__(self, obj):
super(Text, self).__init__(obj, "Text")
self.init_properties(obj)
obj.Proxy = self
def init_properties(self, obj):
"""Add Text specific properties to the object and set them"""
obj.addProperty("App::PropertyPlacement",
"Placement",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The placement of this object"))
obj.addProperty("App::PropertyStringList",
"Text",
"Base",
QT_TRANSLATE_NOOP("App::Property",
"The text displayed by this object"))
def execute(self,obj):
'''Do something when recompute object'''
return
def onChanged(self,obj,prop):
'''Do something when a property has changed'''
return
@@ -1,9 +1,3 @@
"""This module provides the task panel for the Draft CircularArray tool.
"""
## @package task_circulararray
# \ingroup DRAFT
# \brief This module provides the task panel code for the CircularArray tool.
# ***************************************************************************
# * (c) 2019 Eliud Cabrera Castillo <[email protected]> *
# * *
@@ -26,183 +20,257 @@
# * USA *
# * *
# ***************************************************************************
"""Provides the task panel code for the Draft CircularArray tool."""
## @package task_circulararray
# \ingroup DRAFT
# \brief This module provides the task panel code for the CircularArray tool.
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
# import Draft
import Draft_rc
import Draft_rc # include resources, icons, ui files
import DraftVecUtils
import draftutils.utils as utils
from draftutils.messages import _msg, _wrn, _err, _log
from draftutils.translate import _tr
from FreeCAD import Units as U
import PySide.QtCore as QtCore
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
# import DraftTools
from DraftGui import translate
# from DraftGui import displayExternal
_Quantity = App.Units.Quantity
def _Msg(text, end="\n"):
"""Print message with newline"""
App.Console.PrintMessage(text + end)
def _Wrn(text, end="\n"):
"""Print warning with newline"""
App.Console.PrintWarning(text + end)
def _tr(text):
"""Function to translate with the context set"""
return translate("Draft", text)
# So the resource file doesn't trigger errors from code checkers (flake8)
True if Draft_rc.__name__ else False
# The module is used to prevent complaints from code checkers (flake8)
bool(Draft_rc.__name__)
class TaskPanelCircularArray:
"""TaskPanel code for the CircularArray command.
The names of the widgets are defined in the `.ui` file.
In this class all those widgets are automatically created
under the name `self.form.<name>`
This `.ui` file `must` be loaded into an attribute
called `self.form` so that it is loaded into the task panel correctly.
In this class all widgets are automatically created
as `self.form.<widget_name>`.
The `.ui` file may use special FreeCAD widgets such as
`Gui::InputField` (based on `QLineEdit`) and
`Gui::QuantitySpinBox` (based on `QAbstractSpinBox`).
See the Doxygen documentation of the corresponding files in `src/Gui/`,
for example, `InputField.h` and `QuantitySpinBox.h`.
Attributes
----------
source_command: gui_base.GuiCommandBase
This attribute holds a reference to the calling class
of this task panel.
This parent class, which is derived from `gui_base.GuiCommandBase`,
is responsible for calling this task panel, for installing
certain callbacks, and for removing them.
It also delays the execution of the internal creation commands
by using the `draftutils.todo.ToDo` class.
See Also
--------
* https://forum.freecadweb.org/viewtopic.php?f=10&t=40007
* https://forum.freecadweb.org/viewtopic.php?t=5374#p43038
"""
def __init__(self):
self.name = "Circular array"
_log(_tr("Task panel:") + "{}".format(_tr(self.name)))
# The .ui file must be loaded into an attribute
# called `self.form` so that it is displayed in the task panel.
ui_file = ":/ui/TaskPanel_CircularArray.ui"
self.form = Gui.PySideUic.loadUi(ui_file)
self.name = self.form.windowTitle()
icon_name = "Draft_CircularArray"
svg = ":/icons/" + icon_name
pix = QtGui.QPixmap(svg)
icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg))
self.form.setWindowIcon(icon)
self.form.setWindowTitle(_tr(self.name))
self.form.label_icon.setPixmap(pix.scaled(32, 32))
start_distance = _Quantity(1000.0, App.Units.Length)
distance_unit = start_distance.getUserPreferred()[2]
self.form.spinbox_r_distance.setProperty('rawValue',
2 * start_distance.Value)
self.form.spinbox_r_distance.setProperty('unit', distance_unit)
self.form.spinbox_tan_distance.setProperty('rawValue',
start_distance.Value)
self.form.spinbox_tan_distance.setProperty('unit', distance_unit)
# -------------------------------------------------------------------
# Default values for the internal function,
# and for the task panel interface
start_distance = U.Quantity(50.0, App.Units.Length)
length_unit = start_distance.getUserPreferred()[2]
self.r_distance = 2 * start_distance.Value
self.tan_distance = start_distance.Value
self.form.spinbox_number.setValue(3)
self.form.spinbox_symmetry.setValue(1)
self.form.spinbox_r_distance.setProperty('rawValue',
self.r_distance)
self.form.spinbox_r_distance.setProperty('unit', length_unit)
self.form.spinbox_tan_distance.setProperty('rawValue',
self.tan_distance)
self.form.spinbox_tan_distance.setProperty('unit', length_unit)
self.number = self.form.spinbox_number.value()
self.symmetry = self.form.spinbox_symmetry.value()
self.number = 3
self.symmetry = 1
self.form.spinbox_number.setValue(self.number)
self.form.spinbox_symmetry.setValue(self.symmetry)
# TODO: the axis is currently fixed, it should be editable
# or selectable from the task panel
self.axis = App.Vector(0, 0, 1)
start_point = _Quantity(0.0, App.Units.Length)
start_point = U.Quantity(0.0, App.Units.Length)
length_unit = start_point.getUserPreferred()[2]
self.form.input_c_x.setProperty('rawValue', start_point.Value)
self.center = App.Vector(start_point.Value,
start_point.Value,
start_point.Value)
self.form.input_c_x.setProperty('rawValue', self.center.x)
self.form.input_c_x.setProperty('unit', length_unit)
self.form.input_c_y.setProperty('rawValue', start_point.Value)
self.form.input_c_y.setProperty('rawValue', self.center.y)
self.form.input_c_y.setProperty('unit', length_unit)
self.form.input_c_z.setProperty('rawValue', start_point.Value)
self.form.input_c_z.setProperty('rawValue', self.center.z)
self.form.input_c_z.setProperty('unit', length_unit)
self.valid_input = True
self.c_x_str = ""
self.c_y_str = ""
self.c_z_str = ""
self.center = App.Vector(0, 0, 0)
self.fuse = utils.get_param("Draft_array_fuse", False)
self.use_link = utils.get_param("Draft_array_Link", True)
# Old style for Qt4
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
# New style for Qt5
self.form.button_reset.clicked.connect(self.reset_point)
self.form.checkbox_fuse.setChecked(self.fuse)
self.form.checkbox_link.setChecked(self.use_link)
# -------------------------------------------------------------------
# Some objects need to be selected before we can execute the function.
self.selection = None
# This is used to test the input of the internal function.
# It should be changed to True before we can execute the function.
self.valid_input = False
self.set_widget_callbacks()
self.tr_true = QT_TRANSLATE_NOOP("Draft", "True")
self.tr_false = QT_TRANSLATE_NOOP("Draft", "False")
# The mask is not used at the moment, but could be used in the future
# by a callback to restrict the coordinates of the pointer.
self.mask = ""
# When the checkbox changes, change the fuse value
self.fuse = False
QtCore.QObject.connect(self.form.checkbox_fuse,
QtCore.SIGNAL("stateChanged(int)"),
self.set_fuse)
def set_widget_callbacks(self):
"""Set up the callbacks (slots) for the widget signals."""
# New style for Qt5
self.form.button_reset.clicked.connect(self.reset_point)
self.use_link = False
QtCore.QObject.connect(self.form.checkbox_link,
QtCore.SIGNAL("stateChanged(int)"),
self.set_link)
# When the checkbox changes, change the internal value
self.form.checkbox_fuse.stateChanged.connect(self.set_fuse)
self.form.checkbox_link.stateChanged.connect(self.set_link)
# Old style for Qt4, avoid!
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
# QtCore.QObject.connect(self.form.checkbox_fuse,
# QtCore.SIGNAL("stateChanged(int)"),
# self.set_fuse)
# QtCore.QObject.connect(self.form.checkbox_link,
# QtCore.SIGNAL("stateChanged(int)"),
# self.set_link)
def accept(self):
"""Function that executes when clicking the OK button"""
selection = Gui.Selection.getSelection()
self.number = self.form.spinbox_number.value()
"""Execute when clicking the OK button or Enter key."""
self.selection = Gui.Selection.getSelection()
tan_d_str = self.form.spinbox_tan_distance.text()
self.tan_distance = _Quantity(tan_d_str).Value
self.valid_input = self.validate_input(selection,
(self.r_distance,
self.tan_distance) = self.get_distances()
(self.number,
self.symmetry) = self.get_number_symmetry()
self.axis = self.get_axis()
self.center = self.get_center()
self.valid_input = self.validate_input(self.selection,
self.r_distance,
self.tan_distance,
self.number,
self.tan_distance)
self.symmetry,
self.axis,
self.center)
if self.valid_input:
self.create_object(selection)
self.print_messages(selection)
self.create_object()
self.print_messages()
self.finish()
def validate_input(self, selection, number, tan_distance):
"""Check that the input is valid"""
def validate_input(self, selection,
r_distance, tan_distance,
number, symmetry,
axis, center):
"""Check that the input is valid.
Some values may not need to be checked because
the interface may not allow to input wrong data.
"""
if not selection:
_Wrn(_tr("At least one element must be selected"))
_err(_tr("At least one element must be selected."))
return False
if number < 2:
_Wrn(_tr("Number of elements must be at least 2"))
_err(_tr("Number of layers must be at least 2."))
return False
# Todo: each of the elements of the selection could be tested,
# not only the first one.
if selection[0].isDerivedFrom("App::FeaturePython"):
_Wrn(_tr("Selection is not suitable for array"))
_Wrn(_tr("Object:") + " {}".format(selection[0].Label))
# TODO: this should handle multiple objects.
# Each of the elements of the selection should be tested.
obj = selection[0]
if obj.isDerivedFrom("App::FeaturePython"):
_err(_tr("Selection is not suitable for array."))
_err(_tr("Object:") + " {}".format(selection[0].Label))
return False
if r_distance == 0:
_wrn(_tr("Radial distance is zero. "
"Resulting array may not look correct."))
elif r_distance < 0:
_wrn(_tr("Radial distance is negative. "
"It is made positive to proceed."))
self.r_distance = abs(r_distance)
if tan_distance == 0:
_Wrn(_tr("Tangential distance cannot be zero"))
_err(_tr("Tangential distance cannot be zero."))
return False
return True
elif tan_distance < 0:
_wrn(_tr("Tangential distance is negative. "
"It is made positive to proceed."))
self.tan_distance = abs(tan_distance)
def create_object(self, selection):
"""Create the actual object"""
r_d_str = self.form.spinbox_r_distance.text()
tan_d_str = self.form.spinbox_tan_distance.text()
self.r_distance = _Quantity(r_d_str).Value
self.tan_distance = _Quantity(tan_d_str).Value
self.number = self.form.spinbox_number.value()
self.symmetry = self.form.spinbox_symmetry.value()
self.center = self.set_point()
if len(selection) == 1:
sel_obj = selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
# The other arguments are not tested but they should be present.
if symmetry and axis and center:
pass
self.fuse = self.form.checkbox_fuse.isChecked()
self.use_link = self.form.checkbox_link.isChecked()
return True
def create_object(self):
"""Create the new object.
At this stage we already tested that the input is correct
so the necessary attributes are already set.
Then we proceed with the internal function to create the new object.
"""
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
# This creates the object immediately
# obj = Draft.makeArray(sel_obj,
# self.center, self.angle, self.number)
# self.r_distance, self.tan_distance,
# self.axis, self.center,
# self.number, self.symmetry,
# self.use_link)
# if obj:
# obj.Fuse = self.fuse
@@ -210,96 +278,122 @@ class TaskPanelCircularArray:
# of this class, the GuiCommand.
# This is needed to schedule geometry manipulation
# that would crash Coin3D if done in the event callback.
_cmd = "obj = Draft.makeArray("
_cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", "
_cmd += "arg1=" + str(self.r_distance) + ", "
_cmd += "arg2=" + str(self.tan_distance) + ", "
_cmd += "arg3=" + DraftVecUtils.toString(self.axis) + ", "
_cmd += "arg4=" + DraftVecUtils.toString(self.center) + ", "
_cmd += "arg5=" + str(self.number) + ", "
_cmd += "arg6=" + str(self.symmetry) + ", "
_cmd = "draftobjects.circulararray.make_circular_array"
_cmd += "("
_cmd += "App.ActiveDocument." + sel_obj.Name + ", "
_cmd += "r_distance=" + str(self.r_distance) + ", "
_cmd += "tan_distance=" + str(self.tan_distance) + ", "
_cmd += "number=" + str(self.number) + ", "
_cmd += "symmetry=" + str(self.symmetry) + ", "
_cmd += "axis=" + DraftVecUtils.toString(self.axis) + ", "
_cmd += "center=" + DraftVecUtils.toString(self.center) + ", "
_cmd += "use_link=" + str(self.use_link)
_cmd += ")"
_cmd_list = ["FreeCADGui.addModule('Draft')",
_cmd,
_cmd_list = ["Gui.addModule('Draft')",
"Gui.addModule('draftobjects.circulararray')",
"obj = " + _cmd,
"obj.Fuse = " + str(self.fuse),
"Draft.autogroup(obj)",
"FreeCAD.ActiveDocument.recompute()"]
self.source_command.commit("Circular array", _cmd_list)
"App.ActiveDocument.recompute()"]
def set_point(self):
"""Assign the values to the center"""
self.c_x_str = self.form.input_c_x.text()
self.c_y_str = self.form.input_c_y.text()
self.c_z_str = self.form.input_c_z.text()
center = App.Vector(_Quantity(self.c_x_str).Value,
_Quantity(self.c_y_str).Value,
_Quantity(self.c_z_str).Value)
# We commit the command list through the parent command
self.source_command.commit(_tr(self.name), _cmd_list)
def get_distances(self):
"""Get the distance parameters from the widgets."""
r_d_str = self.form.spinbox_r_distance.text()
tan_d_str = self.form.spinbox_tan_distance.text()
return (U.Quantity(r_d_str).Value,
U.Quantity(tan_d_str).Value)
def get_number_symmetry(self):
"""Get the number and symmetry parameters from the widgets."""
number = self.form.spinbox_number.value()
symmetry = self.form.spinbox_symmetry.value()
return number, symmetry
def get_center(self):
"""Get the value of the center from the widgets."""
c_x_str = self.form.input_c_x.text()
c_y_str = self.form.input_c_y.text()
c_z_str = self.form.input_c_z.text()
center = App.Vector(U.Quantity(c_x_str).Value,
U.Quantity(c_y_str).Value,
U.Quantity(c_z_str).Value)
return center
def get_axis(self):
"""Get the axis that will be used for the array. NOT IMPLEMENTED.
It should consider a second selection of an edge or wire to use
as an axis.
"""
return self.axis
def reset_point(self):
"""Reset the point to the original distance"""
"""Reset the center point to the original distance."""
self.form.input_c_x.setProperty('rawValue', 0)
self.form.input_c_y.setProperty('rawValue', 0)
self.form.input_c_z.setProperty('rawValue', 0)
self.center = self.set_point()
_Msg(_tr("Center reset:")
self.center = self.get_center()
_msg(_tr("Center reset:")
+ " ({0}, {1}, {2})".format(self.center.x,
self.center.y,
self.center.z))
def print_fuse_state(self):
"""Print the state translated"""
if self.fuse:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_fuse_state(self, fuse):
"""Print the fuse state translated."""
if fuse:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Fuse:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Fuse:") + " {}".format(state))
def set_fuse(self):
"""This function is called when the fuse checkbox changes"""
"""Execute as a callback when the fuse checkbox changes."""
self.fuse = self.form.checkbox_fuse.isChecked()
self.print_fuse_state()
self.print_fuse_state(self.fuse)
utils.set_param("Draft_array_fuse", self.fuse)
def print_link_state(self):
"""Print the state translated"""
if self.use_link:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_link_state(self, use_link):
"""Print the link state translated."""
if use_link:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Use Link object:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Create Link array:") + " {}".format(state))
def set_link(self):
"""This function is called when the fuse checkbox changes"""
"""Execute as a callback when the link checkbox changes."""
self.use_link = self.form.checkbox_link.isChecked()
self.print_link_state()
self.print_link_state(self.use_link)
utils.set_param("Draft_array_Link", self.use_link)
def print_messages(self, selection):
"""Print messages about the operation"""
if len(selection) == 1:
sel_obj = selection[0]
def print_messages(self):
"""Print messages about the operation."""
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
_Msg("{}".format(16*"-"))
_Msg("{}".format(self.name))
_Msg(_tr("Object:") + " {}".format(sel_obj.Label))
_Msg(_tr("Radial distance:") + " {}".format(self.r_distance))
_Msg(_tr("Tangential distance:") + " {}".format(self.tan_distance))
_Msg(_tr("Number of circular layers:") + " {}".format(self.number))
_Msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry))
_Msg(_tr("Center of rotation:")
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
_msg(_tr("Object:") + " {}".format(sel_obj.Label))
_msg(_tr("Radial distance:") + " {}".format(self.r_distance))
_msg(_tr("Tangential distance:") + " {}".format(self.tan_distance))
_msg(_tr("Number of circular layers:") + " {}".format(self.number))
_msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry))
_msg(_tr("Center of rotation:")
+ " ({0}, {1}, {2})".format(self.center.x,
self.center.y,
self.center.z))
self.print_fuse_state()
self.print_link_state()
self.print_fuse_state(self.fuse)
self.print_link_state(self.use_link)
def display_point(self, point=None, plane=None, mask=None):
"""Displays the coordinates in the x, y, and z widgets.
"""Display the coordinates in the x, y, and z widgets.
This function should be used in a Coin callback so that
the coordinate values are automatically updated when the
@@ -307,21 +401,21 @@ class TaskPanelCircularArray:
This was copied from `DraftGui.py` but needs to be improved
for this particular command.
point :
point: Base::Vector3
is a vector that arrives by the callback.
plane :
plane: WorkingPlane
is a `WorkingPlane` instance, for example,
`App.DraftWorkingPlane`. It is not used at the moment,
but could be used to set up the grid.
mask :
mask: str
is a string that specifies which coordinate is being
edited. It is used to restrict edition of a single coordinate.
It is not used at the moment but could be used with a callback.
"""
# Get the coordinates to display
dp = None
d_p = None
if point:
dp = point
d_p = point
# Set the widgets to the value of the mouse pointer.
#
@@ -336,25 +430,28 @@ class TaskPanelCircularArray:
# sbx = self.form.spinbox_c_x
# sby = self.form.spinbox_c_y
# sbz = self.form.spinbox_c_z
if dp:
if d_p:
if self.mask in ('y', 'z'):
# sbx.setText(displayExternal(dp.x, None, 'Length'))
self.form.input_c_x.setProperty('rawValue', dp.x)
# sbx.setText(displayExternal(d_p.x, None, 'Length'))
self.form.input_c_x.setProperty('rawValue', d_p.x)
else:
# sbx.setText(displayExternal(dp.x, None, 'Length'))
self.form.input_c_x.setProperty('rawValue', dp.x)
# sbx.setText(displayExternal(d_p.x, None, 'Length'))
self.form.input_c_x.setProperty('rawValue', d_p.x)
if self.mask in ('x', 'z'):
# sby.setText(displayExternal(dp.y, None, 'Length'))
self.form.input_c_y.setProperty('rawValue', dp.y)
# sby.setText(displayExternal(d_p.y, None, 'Length'))
self.form.input_c_y.setProperty('rawValue', d_p.y)
else:
# sby.setText(displayExternal(dp.y, None, 'Length'))
self.form.input_c_y.setProperty('rawValue', dp.y)
# sby.setText(displayExternal(d_p.y, None, 'Length'))
self.form.input_c_y.setProperty('rawValue', d_p.y)
if self.mask in ('x', 'y'):
# sbz.setText(displayExternal(dp.z, None, 'Length'))
self.form.input_c_z.setProperty('rawValue', dp.z)
# sbz.setText(displayExternal(d_p.z, None, 'Length'))
self.form.input_c_z.setProperty('rawValue', d_p.z)
else:
# sbz.setText(displayExternal(dp.z, None, 'Length'))
self.form.input_c_z.setProperty('rawValue', dp.z)
# sbz.setText(displayExternal(d_p.z, None, 'Length'))
self.form.input_c_z.setProperty('rawValue', d_p.z)
if plane:
pass
# Set masks
if (mask == "x") or (self.mask == "x"):
@@ -379,7 +476,7 @@ class TaskPanelCircularArray:
self.set_focus()
def set_focus(self, key=None):
"""Set the focus on the widget that receives the key signal"""
"""Set the focus on the widget that receives the key signal."""
if key is None or key == "x":
self.form.input_c_x.setFocus()
self.form.input_c_x.selectAll()
@@ -391,12 +488,16 @@ class TaskPanelCircularArray:
self.form.input_c_z.selectAll()
def reject(self):
"""Function that executes when clicking the Cancel button"""
_Msg(_tr("Aborted:") + " {}".format(self.name))
"""Execute when clicking the Cancel button or pressing Escape."""
_msg(_tr("Aborted:") + " {}".format(_tr(self.name)))
self.finish()
def finish(self):
"""Function that runs at the end after OK or Cancel"""
"""Finish the command, after accept or reject.
It finally calls the parent class to execute
the delayed functions, and perform cleanup.
"""
# App.ActiveDocument.commitTransaction()
Gui.ActiveDocument.resetEdit()
# Runs the parent command to complete the call
+265 -202
View File
@@ -1,8 +1,3 @@
"""Provide the task panel for the Draft OrthoArray tool."""
## @package task_orthoarray
# \ingroup DRAFT
# \brief Provide the task panel for the Draft OrthoArray tool.
# ***************************************************************************
# * (c) 2020 Eliud Cabrera Castillo <[email protected]> *
# * *
@@ -25,175 +20,227 @@
# * USA *
# * *
# ***************************************************************************
import FreeCAD as App
import FreeCADGui as Gui
# import Draft
import Draft_rc
import DraftVecUtils
"""Provides the task panel for the Draft OrthoArray tool."""
## @package task_orthoarray
# \ingroup DRAFT
# \brief Provide the task panel for the Draft OrthoArray tool.
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
# import DraftTools
from draftutils.translate import translate
# from DraftGui import displayExternal
_Quantity = App.Units.Quantity
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc # include resources, icons, ui files
import DraftVecUtils
import draftutils.utils as utils
from draftutils.messages import _msg, _err, _log
from draftutils.translate import _tr
from FreeCAD import Units as U
def _Msg(text, end="\n"):
"""Print message with newline."""
App.Console.PrintMessage(text + end)
def _Wrn(text, end="\n"):
"""Print warning with newline."""
App.Console.PrintWarning(text + end)
def _tr(text):
"""Translate with the context set."""
return translate("Draft", text)
# So the resource file doesn't trigger errors from code checkers (flake8)
True if Draft_rc else False
# The module is used to prevent complaints from code checkers (flake8)
bool(Draft_rc.__name__)
class TaskPanelOrthoArray:
"""TaskPanel for the OrthoArray command.
"""TaskPanel code for the OrthoArray command.
The names of the widgets are defined in the `.ui` file.
In this class all those widgets are automatically created
under the name `self.form.<name>`
This `.ui` file `must` be loaded into an attribute
called `self.form` so that it is loaded into the task panel correctly.
In this class all widgets are automatically created
as `self.form.<widget_name>`.
The `.ui` file may use special FreeCAD widgets such as
`Gui::InputField` (based on `QLineEdit`) and
`Gui::QuantitySpinBox` (based on `QAbstractSpinBox`).
See the Doxygen documentation of the corresponding files in `src/Gui/`,
for example, `InputField.h` and `QuantitySpinBox.h`.
Attributes
----------
source_command: gui_base.GuiCommandBase
This attribute holds a reference to the calling class
of this task panel.
This parent class, which is derived from `gui_base.GuiCommandBase`,
is responsible for calling this task panel, for installing
certain callbacks, and for removing them.
It also delays the execution of the internal creation commands
by using the `draftutils.todo.ToDo` class.
See Also
--------
* https://forum.freecadweb.org/viewtopic.php?f=10&t=40007
* https://forum.freecadweb.org/viewtopic.php?t=5374#p43038
"""
def __init__(self):
self.name = "Orthogonal array"
_log(_tr("Task panel:") + "{}".format(_tr(self.name)))
# The .ui file must be loaded into an attribute
# called `self.form` so that it is displayed in the task panel.
ui_file = ":/ui/TaskPanel_OrthoArray.ui"
self.form = Gui.PySideUic.loadUi(ui_file)
self.name = self.form.windowTitle()
icon_name = "Draft_Array"
svg = ":/icons/" + icon_name
pix = QtGui.QPixmap(svg)
icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg))
self.form.setWindowIcon(icon)
self.form.setWindowTitle(_tr(self.name))
self.form.label_icon.setPixmap(pix.scaled(32, 32))
start_x = _Quantity(100.0, App.Units.Length)
# -------------------------------------------------------------------
# Default values for the internal function,
# and for the task panel interface
start_x = U.Quantity(100.0, App.Units.Length)
start_y = start_x
start_z = start_x
start_zero = _Quantity(0.0, App.Units.Length)
length_unit = start_x.getUserPreferred()[2]
self.form.input_X_x.setProperty('rawValue', start_x.Value)
self.v_x = App.Vector(start_x.Value, 0, 0)
self.v_y = App.Vector(0, start_y.Value, 0)
self.v_z = App.Vector(0, 0, start_z.Value)
self.form.input_X_x.setProperty('rawValue', self.v_x.x)
self.form.input_X_x.setProperty('unit', length_unit)
self.form.input_X_y.setProperty('rawValue', start_zero.Value)
self.form.input_X_y.setProperty('rawValue', self.v_x.y)
self.form.input_X_y.setProperty('unit', length_unit)
self.form.input_X_z.setProperty('rawValue', start_zero.Value)
self.form.input_X_z.setProperty('rawValue', self.v_x.z)
self.form.input_X_z.setProperty('unit', length_unit)
self.form.input_Y_x.setProperty('rawValue', start_zero.Value)
self.form.input_Y_x.setProperty('rawValue', self.v_y.x)
self.form.input_Y_x.setProperty('unit', length_unit)
self.form.input_Y_y.setProperty('rawValue', start_y.Value)
self.form.input_Y_y.setProperty('rawValue', self.v_y.y)
self.form.input_Y_y.setProperty('unit', length_unit)
self.form.input_Y_z.setProperty('rawValue', start_zero.Value)
self.form.input_Y_z.setProperty('rawValue', self.v_y.z)
self.form.input_Y_z.setProperty('unit', length_unit)
self.form.input_Z_x.setProperty('rawValue', start_zero.Value)
self.form.input_Z_x.setProperty('rawValue', self.v_z.x)
self.form.input_Z_x.setProperty('unit', length_unit)
self.form.input_Z_y.setProperty('rawValue', start_zero.Value)
self.form.input_Z_y.setProperty('rawValue', self.v_z.y)
self.form.input_Z_y.setProperty('unit', length_unit)
self.form.input_Z_z.setProperty('rawValue', start_z.Value)
self.form.input_Z_z.setProperty('rawValue', self.v_z.z)
self.form.input_Z_z.setProperty('unit', length_unit)
self.v_X = App.Vector(100, 0, 0)
self.v_Y = App.Vector(0, 100, 0)
self.v_Z = App.Vector(0, 0, 100)
self.n_x = 2
self.n_y = 2
self.n_z = 1
# Old style for Qt4, avoid!
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
self.form.spinbox_n_X.setValue(self.n_x)
self.form.spinbox_n_Y.setValue(self.n_y)
self.form.spinbox_n_Z.setValue(self.n_z)
self.fuse = utils.get_param("Draft_array_fuse", False)
self.use_link = utils.get_param("Draft_array_Link", True)
self.form.checkbox_fuse.setChecked(self.fuse)
self.form.checkbox_link.setChecked(self.use_link)
# -------------------------------------------------------------------
# Some objects need to be selected before we can execute the function.
self.selection = None
# This is used to test the input of the internal function.
# It should be changed to True before we can execute the function.
self.valid_input = False
self.set_widget_callbacks()
self.tr_true = QT_TRANSLATE_NOOP("Draft", "True")
self.tr_false = QT_TRANSLATE_NOOP("Draft", "False")
def set_widget_callbacks(self):
"""Set up the callbacks (slots) for the widget signals."""
# New style for Qt5
self.form.button_reset_X.clicked.connect(lambda: self.reset_v("X"))
self.form.button_reset_Y.clicked.connect(lambda: self.reset_v("Y"))
self.form.button_reset_Z.clicked.connect(lambda: self.reset_v("Z"))
self.n_X = 2
self.n_Y = 2
self.n_Z = 1
self.form.spinbox_n_X.setValue(self.n_X)
self.form.spinbox_n_Y.setValue(self.n_Y)
self.form.spinbox_n_Z.setValue(self.n_Z)
self.valid_input = False
# When the checkbox changes, change the fuse value
self.fuse = False
# When the checkbox changes, change the internal value
self.form.checkbox_fuse.stateChanged.connect(self.set_fuse)
self.use_link = False
self.form.checkbox_link.stateChanged.connect(self.set_link)
# Old style for Qt4, avoid!
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
def accept(self):
"""Execute when clicking the OK button."""
selection = Gui.Selection.getSelection()
n_X = self.form.spinbox_n_X.value()
n_Y = self.form.spinbox_n_Y.value()
n_Z = self.form.spinbox_n_Z.value()
self.valid_input = self.validate_input(selection,
n_X,
n_Y,
n_Z)
"""Execute when clicking the OK button or Enter key."""
self.selection = Gui.Selection.getSelection()
(self.v_x,
self.v_y,
self.v_z) = self.get_intervals()
(self.n_x,
self.n_y,
self.n_z) = self.get_numbers()
self.valid_input = self.validate_input(self.selection,
self.v_x, self.v_y, self.v_z,
self.n_x, self.n_y, self.n_z)
if self.valid_input:
self.create_object(selection)
self.print_messages(selection)
self.create_object()
self.print_messages()
self.finish()
def validate_input(self, selection, n_X, n_Y, n_Z):
"""Check that the input is valid."""
def validate_input(self, selection,
v_x, v_y, v_z,
n_x, n_y, n_z):
"""Check that the input is valid.
Some values may not need to be checked because
the interface may not allow to input wrong data.
"""
if not selection:
_Wrn(_tr("At least one element must be selected"))
_err(_tr("At least one element must be selected."))
return False
if n_X < 1 or n_Y < 1 or n_Z < 1:
_Wrn(_tr("Number of elements must be at least 1"))
if n_x < 1 or n_y < 1 or n_z < 1:
_err(_tr("Number of elements must be at least 1."))
return False
# Todo: each of the elements of the selection could be tested,
# not only the first one.
# TODO: this should handle multiple objects.
# Each of the elements of the selection should be tested.
obj = selection[0]
if obj.isDerivedFrom("App::FeaturePython"):
_Wrn(_tr("Selection is not suitable for array"))
_Wrn(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId))
_err(_tr("Selection is not suitable for array."))
_err(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId))
return False
return True
def create_object(self, selection):
"""Create the actual object."""
self.v_X, self.v_Y, self.v_Z = self.set_intervals()
self.n_X, self.n_Y, self.n_Z = self.set_numbers()
if len(selection) == 1:
sel_obj = selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
# The other arguments are not tested but they should be present.
if v_x and v_y and v_z:
pass
self.fuse = self.form.checkbox_fuse.isChecked()
self.use_link = self.form.checkbox_link.isChecked()
return True
def create_object(self):
"""Create the new object.
At this stage we already tested that the input is correct
so the necessary attributes are already set.
Then we proceed with the internal function to create the new object.
"""
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
# This creates the object immediately
# obj = Draft.makeArray(sel_obj,
# self.v_X, self.v_Y, self.v_Z,
# self.n_X, self.n_Y, self.n_Z)
# self.v_x, self.v_y, self.v_z,
# self.n_x, self.n_y, self.n_z,
# self.use_link)
# if obj:
# obj.Fuse = self.fuse
@@ -201,146 +248,162 @@ class TaskPanelOrthoArray:
# of this class, the GuiCommand.
# This is needed to schedule geometry manipulation
# that would crash Coin3D if done in the event callback.
_cmd = "obj = Draft.makeArray("
_cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", "
_cmd += "arg1=" + DraftVecUtils.toString(self.v_X) + ", "
_cmd += "arg2=" + DraftVecUtils.toString(self.v_Y) + ", "
_cmd += "arg3=" + DraftVecUtils.toString(self.v_Z) + ", "
_cmd += "arg4=" + str(self.n_X) + ", "
_cmd += "arg5=" + str(self.n_Y) + ", "
_cmd += "arg6=" + str(self.n_Z) + ", "
_cmd = "draftobjects.orthoarray.make_ortho_array"
_cmd += "("
_cmd += "App.ActiveDocument." + sel_obj.Name + ", "
_cmd += "v_x=" + DraftVecUtils.toString(self.v_x) + ", "
_cmd += "v_y=" + DraftVecUtils.toString(self.v_y) + ", "
_cmd += "v_z=" + DraftVecUtils.toString(self.v_z) + ", "
_cmd += "n_x=" + str(self.n_x) + ", "
_cmd += "n_y=" + str(self.n_y) + ", "
_cmd += "n_z=" + str(self.n_z) + ", "
_cmd += "use_link=" + str(self.use_link)
_cmd += ")"
_cmd_list = ["FreeCADGui.addModule('Draft')",
_cmd,
_cmd_list = ["Gui.addModule('Draft')",
"Gui.addModule('draftobjects.orthoarray')",
"obj = " + _cmd,
"obj.Fuse = " + str(self.fuse),
"Draft.autogroup(obj)",
"FreeCAD.ActiveDocument.recompute()"]
self.source_command.commit("Ortho array", _cmd_list)
"App.ActiveDocument.recompute()"]
def set_numbers(self):
"""Assign the number of elements."""
self.n_X = self.form.spinbox_n_X.value()
self.n_Y = self.form.spinbox_n_Y.value()
self.n_Z = self.form.spinbox_n_Z.value()
return self.n_X, self.n_Y, self.n_Z
# We commit the command list through the parent command
self.source_command.commit(_tr(self.name), _cmd_list)
def set_intervals(self):
"""Assign the interval vectors."""
v_X_x_str = self.form.input_X_x.text()
v_X_y_str = self.form.input_X_y.text()
v_X_z_str = self.form.input_X_z.text()
self.v_X = App.Vector(_Quantity(v_X_x_str).Value,
_Quantity(v_X_y_str).Value,
_Quantity(v_X_z_str).Value)
def get_numbers(self):
"""Get the number of elements from the widgets."""
return (self.form.spinbox_n_X.value(),
self.form.spinbox_n_Y.value(),
self.form.spinbox_n_Z.value())
v_Y_x_str = self.form.input_Y_x.text()
v_Y_y_str = self.form.input_Y_y.text()
v_Y_z_str = self.form.input_Y_z.text()
self.v_Y = App.Vector(_Quantity(v_Y_x_str).Value,
_Quantity(v_Y_y_str).Value,
_Quantity(v_Y_z_str).Value)
def get_intervals(self):
"""Get the interval vectors from the widgets."""
v_x_x_str = self.form.input_X_x.text()
v_x_y_str = self.form.input_X_y.text()
v_x_z_str = self.form.input_X_z.text()
v_x = App.Vector(U.Quantity(v_x_x_str).Value,
U.Quantity(v_x_y_str).Value,
U.Quantity(v_x_z_str).Value)
v_Z_x_str = self.form.input_Z_x.text()
v_Z_y_str = self.form.input_Z_y.text()
v_Z_z_str = self.form.input_Z_z.text()
self.v_Z = App.Vector(_Quantity(v_Z_x_str).Value,
_Quantity(v_Z_y_str).Value,
_Quantity(v_Z_z_str).Value)
return self.v_X, self.v_Y, self.v_Z
v_y_x_str = self.form.input_Y_x.text()
v_y_y_str = self.form.input_Y_y.text()
v_y_z_str = self.form.input_Y_z.text()
v_y = App.Vector(U.Quantity(v_y_x_str).Value,
U.Quantity(v_y_y_str).Value,
U.Quantity(v_y_z_str).Value)
v_z_x_str = self.form.input_Z_x.text()
v_z_y_str = self.form.input_Z_y.text()
v_z_z_str = self.form.input_Z_z.text()
v_z = App.Vector(U.Quantity(v_z_x_str).Value,
U.Quantity(v_z_y_str).Value,
U.Quantity(v_z_z_str).Value)
return v_x, v_y, v_z
def reset_v(self, interval):
"""Reset the interval to zero distance."""
"""Reset the interval to zero distance.
Parameters
----------
interval: str
Either "X", "Y", "Z", to reset the interval vector
for that direction.
"""
if interval == "X":
self.form.input_X_x.setProperty('rawValue', 100)
self.form.input_X_y.setProperty('rawValue', 0)
self.form.input_X_z.setProperty('rawValue', 0)
_Msg(_tr("Interval X reset:")
+ " ({0}, {1}, {2})".format(self.v_X.x,
self.v_X.y,
self.v_X.z))
self.v_x, self.v_y, self.v_z = self.get_intervals()
_msg(_tr("Interval X reset:")
+ " ({0}, {1}, {2})".format(self.v_x.x,
self.v_x.y,
self.v_x.z))
elif interval == "Y":
self.form.input_Y_x.setProperty('rawValue', 0)
self.form.input_Y_y.setProperty('rawValue', 100)
self.form.input_Y_z.setProperty('rawValue', 0)
_Msg(_tr("Interval Y reset:")
+ " ({0}, {1}, {2})".format(self.v_Y.x,
self.v_Y.y,
self.v_Y.z))
self.v_x, self.v_y, self.v_z = self.get_intervals()
_msg(_tr("Interval Y reset:")
+ " ({0}, {1}, {2})".format(self.v_y.x,
self.v_y.y,
self.v_y.z))
elif interval == "Z":
self.form.input_Z_x.setProperty('rawValue', 0)
self.form.input_Z_y.setProperty('rawValue', 0)
self.form.input_Z_z.setProperty('rawValue', 100)
_Msg(_tr("Interval Z reset:")
+ " ({0}, {1}, {2})".format(self.v_Z.x,
self.v_Z.y,
self.v_Z.z))
self.v_x, self.v_y, self.v_z = self.get_intervals()
_msg(_tr("Interval Z reset:")
+ " ({0}, {1}, {2})".format(self.v_z.x,
self.v_z.y,
self.v_z.z))
self.n_X, self.n_Y, self.n_Z = self.set_intervals()
def print_fuse_state(self):
"""Print the state translated."""
if self.fuse:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_fuse_state(self, fuse):
"""Print the fuse state translated."""
if fuse:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Fuse:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Fuse:") + " {}".format(state))
def set_fuse(self):
"""Run callback when the fuse checkbox changes."""
"""Execute as a callback when the fuse checkbox changes."""
self.fuse = self.form.checkbox_fuse.isChecked()
self.print_fuse_state()
self.print_fuse_state(self.fuse)
utils.set_param("Draft_array_fuse", self.fuse)
def print_link_state(self):
"""Print the state translated."""
if self.use_link:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_link_state(self, use_link):
"""Print the link state translated."""
if use_link:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Use Link object:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Create Link array:") + " {}".format(state))
def set_link(self):
"""Run callback when the link checkbox changes."""
"""Execute as a callback when the link checkbox changes."""
self.use_link = self.form.checkbox_link.isChecked()
self.print_link_state()
self.print_link_state(self.use_link)
utils.set_param("Draft_array_Link", self.use_link)
def print_messages(self, selection):
def print_messages(self):
"""Print messages about the operation."""
if len(selection) == 1:
sel_obj = selection[0]
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
_Msg("{}".format(16*"-"))
_Msg("{}".format(self.name))
_Msg(_tr("Object:") + " {}".format(sel_obj.Label))
_Msg(_tr("Number of X elements:") + " {}".format(self.n_X))
_Msg(_tr("Interval X:")
+ " ({0}, {1}, {2})".format(self.v_X.x,
self.v_X.y,
self.v_X.z))
_Msg(_tr("Number of Y elements:") + " {}".format(self.n_Y))
_Msg(_tr("Interval Y:")
+ " ({0}, {1}, {2})".format(self.v_Y.x,
self.v_Y.y,
self.v_Y.z))
_Msg(_tr("Number of Z elements:") + " {}".format(self.n_Z))
_Msg(_tr("Interval Z:")
+ " ({0}, {1}, {2})".format(self.v_Z.x,
self.v_Z.y,
self.v_Z.z))
self.print_fuse_state()
self.print_link_state()
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
_msg(_tr("Object:") + " {}".format(sel_obj.Label))
_msg(_tr("Number of X elements:") + " {}".format(self.n_x))
_msg(_tr("Interval X:")
+ " ({0}, {1}, {2})".format(self.v_x.x,
self.v_x.y,
self.v_x.z))
_msg(_tr("Number of Y elements:") + " {}".format(self.n_y))
_msg(_tr("Interval Y:")
+ " ({0}, {1}, {2})".format(self.v_y.x,
self.v_y.y,
self.v_y.z))
_msg(_tr("Number of Z elements:") + " {}".format(self.n_z))
_msg(_tr("Interval Z:")
+ " ({0}, {1}, {2})".format(self.v_z.x,
self.v_z.y,
self.v_z.z))
self.print_fuse_state(self.fuse)
self.print_link_state(self.use_link)
def reject(self):
"""Run when clicking the Cancel button."""
_Msg(_tr("Aborted:") + " {}".format(self.name))
"""Execute when clicking the Cancel button or pressing Escape."""
_msg(_tr("Aborted:") + " {}".format(_tr(self.name)))
self.finish()
def finish(self):
"""Run at the end after OK or Cancel."""
"""Finish the command, after accept or reject.
It finally calls the parent class to execute
the delayed functions, and perform cleanup.
"""
# App.ActiveDocument.commitTransaction()
Gui.ActiveDocument.resetEdit()
# Runs the parent command to complete the call
+228 -156
View File
@@ -1,9 +1,3 @@
"""This module provides the task panel for the Draft PolarArray tool.
"""
## @package task_polararray
# \ingroup DRAFT
# \brief This module provides the task panel code for the PolarArray tool.
# ***************************************************************************
# * (c) 2019 Eliud Cabrera Castillo <[email protected]> *
# * *
@@ -26,163 +20,221 @@
# * USA *
# * *
# ***************************************************************************
"""Provides the task panel for the Draft PolarArray tool."""
## @package task_polararray
# \ingroup DRAFT
# \brief This module provides the task panel code for the PolarArray tool.
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
# import Draft
import Draft_rc
import Draft_rc # include resources, icons, ui files
import DraftVecUtils
import draftutils.utils as utils
from draftutils.messages import _msg, _wrn, _err, _log
from draftutils.translate import _tr
from FreeCAD import Units as U
import PySide.QtCore as QtCore
import PySide.QtGui as QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
# import DraftTools
from DraftGui import translate
# from DraftGui import displayExternal
_Quantity = App.Units.Quantity
def _Msg(text, end="\n"):
"""Print message with newline"""
App.Console.PrintMessage(text + end)
def _Wrn(text, end="\n"):
"""Print warning with newline"""
App.Console.PrintWarning(text + end)
def _tr(text):
"""Function to translate with the context set"""
return translate("Draft", text)
# So the resource file doesn't trigger errors from code checkers (flake8)
True if Draft_rc.__name__ else False
# The module is used to prevent complaints from code checkers (flake8)
bool(Draft_rc.__name__)
class TaskPanelPolarArray:
"""TaskPanel for the PolarArray command.
"""TaskPanel code for the PolarArray command.
The names of the widgets are defined in the `.ui` file.
In this class all those widgets are automatically created
under the name `self.form.<name>`
This `.ui` file `must` be loaded into an attribute
called `self.form` so that it is loaded into the task panel correctly.
In this class all widgets are automatically created
as `self.form.<widget_name>`.
The `.ui` file may use special FreeCAD widgets such as
`Gui::InputField` (based on `QLineEdit`) and
`Gui::QuantitySpinBox` (based on `QAbstractSpinBox`).
See the Doxygen documentation of the corresponding files in `src/Gui/`,
for example, `InputField.h` and `QuantitySpinBox.h`.
Attributes
----------
source_command: gui_base.GuiCommandBase
This attribute holds a reference to the calling class
of this task panel.
This parent class, which is derived from `gui_base.GuiCommandBase`,
is responsible for calling this task panel, for installing
certain callbacks, and for removing them.
It also delays the execution of the internal creation commands
by using the `draftutils.todo.ToDo` class.
See Also
--------
* https://forum.freecadweb.org/viewtopic.php?f=10&t=40007
* https://forum.freecadweb.org/viewtopic.php?t=5374#p43038
"""
def __init__(self):
self.name = "Polar array"
_log(_tr("Task panel:") + "{}".format(_tr(self.name)))
# The .ui file must be loaded into an attribute
# called `self.form` so that it is displayed in the task panel.
ui_file = ":/ui/TaskPanel_PolarArray.ui"
self.form = Gui.PySideUic.loadUi(ui_file)
self.name = self.form.windowTitle()
icon_name = "Draft_PolarArray"
svg = ":/icons/" + icon_name
pix = QtGui.QPixmap(svg)
icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg))
self.form.setWindowIcon(icon)
self.form.setWindowTitle(_tr(self.name))
self.form.label_icon.setPixmap(pix.scaled(32, 32))
start_angle = _Quantity(180.0, App.Units.Angle)
# -------------------------------------------------------------------
# Default values for the internal function,
# and for the task panel interface
start_angle = U.Quantity(360.0, App.Units.Angle)
angle_unit = start_angle.getUserPreferred()[2]
self.form.spinbox_angle.setProperty('rawValue', start_angle.Value)
self.form.spinbox_angle.setProperty('unit', angle_unit)
self.form.spinbox_number.setValue(4)
self.angle_str = self.form.spinbox_angle.text()
self.angle = start_angle.Value
self.number = 5
self.number = self.form.spinbox_number.value()
self.form.spinbox_angle.setProperty('rawValue', self.angle)
self.form.spinbox_angle.setProperty('unit', angle_unit)
start_point = _Quantity(0.0, App.Units.Length)
self.form.spinbox_number.setValue(self.number)
start_point = U.Quantity(0.0, App.Units.Length)
length_unit = start_point.getUserPreferred()[2]
self.form.input_c_x.setProperty('rawValue', start_point.Value)
self.center = App.Vector(start_point.Value,
start_point.Value,
start_point.Value)
self.form.input_c_x.setProperty('rawValue', self.center.x)
self.form.input_c_x.setProperty('unit', length_unit)
self.form.input_c_y.setProperty('rawValue', start_point.Value)
self.form.input_c_y.setProperty('rawValue', self.center.y)
self.form.input_c_y.setProperty('unit', length_unit)
self.form.input_c_z.setProperty('rawValue', start_point.Value)
self.form.input_c_z.setProperty('rawValue', self.center.z)
self.form.input_c_z.setProperty('unit', length_unit)
self.valid_input = True
self.c_x_str = ""
self.c_y_str = ""
self.c_z_str = ""
self.center = App.Vector(0, 0, 0)
self.fuse = utils.get_param("Draft_array_fuse", False)
self.use_link = utils.get_param("Draft_array_Link", True)
# Old style for Qt4
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
# New style for Qt5
self.form.button_reset.clicked.connect(self.reset_point)
self.form.checkbox_fuse.setChecked(self.fuse)
self.form.checkbox_link.setChecked(self.use_link)
# -------------------------------------------------------------------
# Some objects need to be selected before we can execute the function.
self.selection = None
# This is used to test the input of the internal function.
# It should be changed to True before we can execute the function.
self.valid_input = False
self.set_widget_callbacks()
self.tr_true = QT_TRANSLATE_NOOP("Draft", "True")
self.tr_false = QT_TRANSLATE_NOOP("Draft", "False")
# The mask is not used at the moment, but could be used in the future
# by a callback to restrict the coordinates of the pointer.
self.mask = ""
# When the checkbox changes, change the fuse value
self.fuse = False
QtCore.QObject.connect(self.form.checkbox_fuse,
QtCore.SIGNAL("stateChanged(int)"),
self.set_fuse)
def set_widget_callbacks(self):
"""Set up the callbacks (slots) for the widget signals."""
# New style for Qt5
self.form.button_reset.clicked.connect(self.reset_point)
self.use_link = False
QtCore.QObject.connect(self.form.checkbox_link,
QtCore.SIGNAL("stateChanged(int)"),
self.set_link)
# When the checkbox changes, change the internal value
self.form.checkbox_fuse.stateChanged.connect(self.set_fuse)
self.form.checkbox_link.stateChanged.connect(self.set_link)
# Old style for Qt4, avoid!
# QtCore.QObject.connect(self.form.button_reset,
# QtCore.SIGNAL("clicked()"),
# self.reset_point)
def accept(self):
"""Function that executes when clicking the OK button"""
selection = Gui.Selection.getSelection()
self.number = self.form.spinbox_number.value()
self.valid_input = self.validate_input(selection,
self.number)
"""Execute when clicking the OK button or Enter key."""
self.selection = Gui.Selection.getSelection()
(self.number,
self.angle) = self.get_number_angle()
self.center = self.get_center()
self.valid_input = self.validate_input(self.selection,
self.number,
self.angle,
self.center)
if self.valid_input:
self.create_object(selection)
self.print_messages(selection)
self.create_object()
self.print_messages()
self.finish()
def validate_input(self, selection, number):
"""Check that the input is valid"""
def validate_input(self, selection,
number, angle, center):
"""Check that the input is valid.
Some values may not need to be checked because
the interface may not allow to input wrong data.
"""
if not selection:
_Wrn(_tr("At least one element must be selected"))
_err(_tr("At least one element must be selected."))
return False
# TODO: this should handle multiple objects.
# Each of the elements of the selection should be tested.
obj = selection[0]
if obj.isDerivedFrom("App::FeaturePython"):
_err(_tr("Selection is not suitable for array."))
_err(_tr("Object:") + " {}".format(selection[0].Label))
return False
if number < 2:
_Wrn(_tr("Number of elements must be at least 2"))
_err(_tr("Number of elements must be at least 2."))
return False
# Todo: each of the elements of the selection could be tested,
# not only the first one.
if selection[0].isDerivedFrom("App::FeaturePython"):
_Wrn(_tr("Selection is not suitable for array"))
_Wrn(_tr("Object:") + " {}".format(selection[0].Label))
return False
return True
def create_object(self, selection):
"""Create the actual object"""
self.angle_str = self.form.spinbox_angle.text()
self.angle = _Quantity(self.angle_str).Value
if angle > 360:
_wrn(_tr("The angle is above 360 degrees. "
"It is set to this value to proceed."))
self.angle = 360
elif angle < -360:
_wrn(_tr("The angle is below -360 degrees. "
"It is set to this value to proceed."))
self.angle = -360
self.center = self.set_point()
if len(selection) == 1:
sel_obj = selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
# The other arguments are not tested but they should be present.
if center:
pass
self.fuse = self.form.checkbox_fuse.isChecked()
self.use_link = self.form.checkbox_link.isChecked()
return True
def create_object(self):
"""Create the new object.
At this stage we already tested that the input is correct
so the necessary attributes are already set.
Then we proceed with the internal function to create the new object.
"""
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
# This creates the object immediately
# obj = Draft.makeArray(sel_obj,
# self.center, self.angle, self.number)
# self.center, self.angle, self.number,
# self.use_link)
# if obj:
# obj.Fuse = self.fuse
@@ -190,91 +242,104 @@ class TaskPanelPolarArray:
# of this class, the GuiCommand.
# This is needed to schedule geometry manipulation
# that would crash Coin3D if done in the event callback.
_cmd = "obj = Draft.makeArray("
_cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", "
_cmd += "arg1=" + DraftVecUtils.toString(self.center) + ", "
_cmd += "arg2=" + str(self.angle) + ", "
_cmd += "arg3=" + str(self.number) + ", "
_cmd = "draftobjects.polararray.make_polar_array"
_cmd += "("
_cmd += "App.ActiveDocument." + sel_obj.Name + ", "
_cmd += "number=" + str(self.number) + ", "
_cmd += "angle=" + str(self.angle) + ", "
_cmd += "center=" + DraftVecUtils.toString(self.center) + ", "
_cmd += "use_link=" + str(self.use_link)
_cmd += ")"
_cmd_list = ["FreeCADGui.addModule('Draft')",
_cmd,
_cmd_list = ["Gui.addModule('Draft')",
"Gui.addModule('draftobjects.polararray')",
"obj = " + _cmd,
"obj.Fuse = " + str(self.fuse),
"Draft.autogroup(obj)",
"FreeCAD.ActiveDocument.recompute()"]
self.source_command.commit("Polar array", _cmd_list)
"App.ActiveDocument.recompute()"]
def set_point(self):
"""Assign the values to the center"""
self.c_x_str = self.form.input_c_x.text()
self.c_y_str = self.form.input_c_y.text()
self.c_z_str = self.form.input_c_z.text()
center = App.Vector(_Quantity(self.c_x_str).Value,
_Quantity(self.c_y_str).Value,
_Quantity(self.c_z_str).Value)
# We commit the command list through the parent command
self.source_command.commit(_tr(self.name), _cmd_list)
def get_number_angle(self):
"""Get the number and angle parameters from the widgets."""
number = self.form.spinbox_number.value()
angle_str = self.form.spinbox_angle.text()
angle = U.Quantity(angle_str).Value
return number, angle
def get_center(self):
"""Get the value of the center from the widgets."""
c_x_str = self.form.input_c_x.text()
c_y_str = self.form.input_c_y.text()
c_z_str = self.form.input_c_z.text()
center = App.Vector(U.Quantity(c_x_str).Value,
U.Quantity(c_y_str).Value,
U.Quantity(c_z_str).Value)
return center
def reset_point(self):
"""Reset the point to the original distance"""
"""Reset the center point to the original distance."""
self.form.input_c_x.setProperty('rawValue', 0)
self.form.input_c_y.setProperty('rawValue', 0)
self.form.input_c_z.setProperty('rawValue', 0)
self.center = self.set_point()
_Msg(_tr("Center reset:")
self.center = self.get_center()
_msg(_tr("Center reset:")
+ " ({0}, {1}, {2})".format(self.center.x,
self.center.y,
self.center.z))
def print_fuse_state(self):
"""Print the state translated"""
if self.fuse:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_fuse_state(self, fuse):
"""Print the fuse state translated."""
if fuse:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Fuse:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Fuse:") + " {}".format(state))
def set_fuse(self):
"""This function is called when the fuse checkbox changes"""
"""Execute as a callback when the fuse checkbox changes."""
self.fuse = self.form.checkbox_fuse.isChecked()
self.print_fuse_state()
self.print_fuse_state(self.fuse)
utils.set_param("Draft_array_fuse", self.fuse)
def print_link_state(self):
"""Print the state translated"""
if self.use_link:
translated_state = QT_TRANSLATE_NOOP("Draft", "True")
def print_link_state(self, use_link):
"""Print the link state translated."""
if use_link:
state = self.tr_true
else:
translated_state = QT_TRANSLATE_NOOP("Draft", "False")
_Msg(_tr("Use Link object:") + " {}".format(translated_state))
state = self.tr_false
_msg(_tr("Create Link array:") + " {}".format(state))
def set_link(self):
"""This function is called when the fuse checkbox changes"""
"""Execute as a callback when the link checkbox changes."""
self.use_link = self.form.checkbox_link.isChecked()
self.print_link_state()
self.print_link_state(self.use_link)
utils.set_param("Draft_array_Link", self.use_link)
def print_messages(self, selection):
"""Print messages about the operation"""
if len(selection) == 1:
sel_obj = selection[0]
def print_messages(self):
"""Print messages about the operation."""
if len(self.selection) == 1:
sel_obj = self.selection[0]
else:
# This can be changed so a compound of multiple
# selected objects is produced
sel_obj = selection[0]
_Msg("{}".format(16*"-"))
_Msg("{}".format(self.name))
_Msg(_tr("Object:") + " {}".format(sel_obj.Label))
_Msg(_tr("Start angle:") + " {}".format(self.angle_str))
_Msg(_tr("Number of elements:") + " {}".format(self.number))
_Msg(_tr("Center of rotation:")
# TODO: this should handle multiple objects.
# For example, it could take the shapes of all objects,
# make a compound and then use it as input for the array function.
sel_obj = self.selection[0]
_msg(_tr("Object:") + " {}".format(sel_obj.Label))
_msg(_tr("Number of elements:") + " {}".format(self.number))
_msg(_tr("Polar angle:") + " {}".format(self.angle))
_msg(_tr("Center of rotation:")
+ " ({0}, {1}, {2})".format(self.center.x,
self.center.y,
self.center.z))
self.print_fuse_state()
self.print_link_state()
self.print_fuse_state(self.fuse)
self.print_link_state(self.use_link)
def display_point(self, point=None, plane=None, mask=None):
"""Displays the coordinates in the x, y, and z widgets.
"""Display the coordinates in the x, y, and z widgets.
This function should be used in a Coin callback so that
the coordinate values are automatically updated when the
@@ -331,6 +396,9 @@ class TaskPanelPolarArray:
# sbz.setText(displayExternal(dp.z, None, 'Length'))
self.form.input_c_z.setProperty('rawValue', dp.z)
if plane:
pass
# Set masks
if (mask == "x") or (self.mask == "x"):
self.form.input_c_x.setEnabled(True)
@@ -354,7 +422,7 @@ class TaskPanelPolarArray:
self.set_focus()
def set_focus(self, key=None):
"""Set the focus on the widget that receives the key signal"""
"""Set the focus on the widget that receives the key signal."""
if key is None or key == "x":
self.form.input_c_x.setFocus()
self.form.input_c_x.selectAll()
@@ -366,12 +434,16 @@ class TaskPanelPolarArray:
self.form.input_c_z.selectAll()
def reject(self):
"""Function that executes when clicking the Cancel button"""
_Msg(_tr("Aborted:") + " {}".format(self.name))
"""Execute when clicking the Cancel button or pressing Escape."""
_msg(_tr("Aborted:") + " {}".format(_tr(self.name)))
self.finish()
def finish(self):
"""Function that runs at the end after OK or Cancel"""
"""Finish the command, after accept or reject.
It finally calls the parent class to execute
the delayed functions, and perform cleanup.
"""
# App.ActiveDocument.commitTransaction()
Gui.ActiveDocument.resetEdit()
# Runs the parent command to complete the call
+59 -46
View File
@@ -98,55 +98,68 @@ def autogroup(obj):
obj: App::DocumentObject
Any type of object that will be stored in the group.
"""
# check for required conditions for autogroup to work
if not App.GuiUp:
return
if not hasattr(Gui,"draftToolBar"):
return
if not hasattr(Gui.draftToolBar,"autogroup"):
return
if Gui.draftToolBar.isConstructionMode():
return
# autogroup code
if Gui.draftToolBar.autogroup is not None:
active_group = App.ActiveDocument.getObject(Gui.draftToolBar.autogroup)
if active_group:
found = False
for o in active_group.Group:
if o.Name == obj.Name:
found = True
if not found:
gr = active_group.Group
gr.append(obj)
active_group.Group = gr
else:
doc = App.ActiveDocument
view = Gui.ActiveDocument.ActiveView
# Look for active Arch container
active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch")
if hasattr(Gui, "draftToolBar"):
if (hasattr(Gui.draftToolBar, "autogroup")
and not Gui.draftToolBar.isConstructionMode()):
if Gui.draftToolBar.autogroup is not None:
active_group = doc.getObject(Gui.draftToolBar.autogroup)
if active_group:
found = False
for o in active_group.Group:
if o.Name == obj.Name:
found = True
if not found:
gr = active_group.Group
gr.append(obj)
active_group.Group = gr
elif active_arch_obj:
active_arch_obj.addObject(obj)
elif view.getActiveObject("part", False) is not None:
# Add object to active part and change its placement
# accordingly so the object does not jump
# to a different position, works with App::Link if not scaled.
# Modified accordingly to realthunder suggestions
p, parent, sub = view.getActiveObject("part", False)
matrix = parent.getSubObject(sub, retType=4)
if matrix.hasScale() == 1:
_msg(translate("Draft",
"Unable to insert new object into "
"a scaled part"))
return
inverse_placement = App.Placement(matrix.inverse())
if get_type(obj) == 'Point':
# point vector have a kind of placement, so should be
# processed before generic object with placement
point_vector = App.Vector(obj.X, obj.Y, obj.Z)
real_point = inverse_placement.multVec(point_vector)
obj.X = real_point.x
obj.Y = real_point.y
obj.Z = real_point.z
elif hasattr(obj, "Placement"):
place = inverse_placement.multiply(obj.Placement)
obj.Placement = App.Placement(place)
p.addObject(obj)
if Gui.ActiveDocument.ActiveView.getActiveObject("Arch"):
# add object to active Arch Container
Gui.ActiveDocument.ActiveView.getActiveObject("Arch").addObject(obj)
elif Gui.ActiveDocument.ActiveView.getActiveObject("part", False) is not None:
# add object to active part and change it's placement accordingly
# so object does not jump to different position, works with App::Link
# if not scaled. Modified accordingly to realthunder suggestions
p, parent, sub = Gui.ActiveDocument.ActiveView.getActiveObject("part", False)
matrix = parent.getSubObject(sub, retType=4)
if matrix.hasScale() == 1:
err = translate("Draft",
"Unable to insert new object into "
"a scaled part")
App.Console.PrintMessage(err)
return
inverse_placement = App.Placement(matrix.inverse())
if get_type(obj) == 'Point':
point_vector = App.Vector(obj.X, obj.Y, obj.Z)
real_point = inverse_placement.multVec(point_vector)
obj.X = real_point.x
obj.Y = real_point.y
obj.Z = real_point.z
elif get_type(obj) in ["Dimension"]:
obj.Start = inverse_placement.multVec(obj.Start)
obj.End = inverse_placement.multVec(obj.End)
obj.Dimline = inverse_placement.multVec(obj.Dimline)
obj.Normal = inverse_placement.Rotation.multVec(obj.Normal)
obj.Direction = inverse_placement.Rotation.multVec(obj.Direction)
elif get_type(obj) in ["Label"]:
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
obj.TargetPoint = inverse_placement.multVec(obj.TargetPoint)
elif hasattr(obj,"Placement"):
# every object that have a placement is processed here
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
p.addObject(obj)
def dim_symbol(symbol=None, invert=False):
@@ -0,0 +1,379 @@
# ***************************************************************************
# * *
# * Copyright (c) 2020 Carlo Pavan <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""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.
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
#----------------------------------------------------------------------------
draft_scales_metrics = ["1:1000", "1:500", "1:250", "1:200", "1:100",
"1:50", "1:25","1:20", "1:10", "1:5","1:2",
"1:1",
"2:1", "5:1", "10:1", "20:1",
QT_TRANSLATE_NOOP("draft","custom"),
]
draft_scales_arch_imperial = ["1/16in=1ft", "3/32in=1ft", "1/8in=1ft",
"3/16in=1ft", "1/4in=1ft","3/8in=1ft",
"1/2in=1ft", "3/4in=1ft", "1in=1ft",
"1.5in=1ft", "3in=1ft",
QT_TRANSLATE_NOOP("draft","custom"),
]
draft_scales_eng_imperial = ["1in=10ft", "1in=20ft", "1in=30ft",
"1in=40ft", "1in=50ft", "1in=60ft",
"1in=70ft", "1in=80ft", "1in=90ft",
"1in=100ft",
QT_TRANSLATE_NOOP("draft","custom"),
]
def get_scales(unit_system = 0):
"""
returns the list of preset scales accordin to unit system.
Parameters:
unit_system = 0 : default from user preferences
1 : metrics
2 : imperial architectural
3 : imperial engineering
"""
if unit_system == 0:
param = App.ParamGet("User parameter:BaseApp/Preferences/Units")
scale_units_system = param.GetInt("UserSchema", 0)
if scale_units_system in [0, 1, 4, 6]:
return draft_scales_metrics
elif scale_units_system in [2, 3, 5]:
return draft_scales_arch_imperial
elif scale_units_system in [7]:
return draft_scales_eng_imperial
elif unit_system == 1:
return draft_scales_metrics
elif unit_system == 2:
return draft_scales_arch_imperial
elif unit_system == 3:
return draft_scales_eng_imperial
def scale_to_label(scale):
"""
transform a float number into a 1:X or X:1 scale and return it as label
"""
f = 1/scale
f = round(f,2)
f = f.as_integer_ratio()
if f[1] == 1 or f[0] == 1:
label = str(f[1]) + ":" + str(f[0])
return label
else:
return str(scale)
def label_to_scale(label):
"""
transform a scale string into scale factor as float
"""
try :
scale = float(label)
return scale
except :
if ":" in label:
f = label.split(":")
elif "=" in label:
f = label.split("=")
else:
return
if len(f) == 2:
try:
num = App.Units.Quantity(f[0]).Value
den = App.Units.Quantity(f[1]).Value
scale = num/den
return scale
except:
err = QT_TRANSLATE_NOOP("draft",
"Unable to convert input into a "
"scale factor")
App.Console.PrintWarning(err)
return None
def _set_scale(action):
"""
triggered by scale pushbutton, set DraftAnnotationScale in preferences
"""
# set the label of the scale button
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
mw = Gui.getMainWindow()
sb = mw.statusBar()
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"
)
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
param.SetFloat("DraftAnnotationScale", scale)
cs = scale_to_label(scale)
scale_widget.scaleLabel.setText(cs)
else:
text_scale = action.text()
scale_widget.scaleLabel.setText(text_scale)
scale = label_to_scale(text_scale)
param.SetFloat("DraftAnnotationScale", scale)
#----------------------------------------------------------------------------
# MAIN DRAFT STATUSBAR FUNCTIONS
#----------------------------------------------------------------------------
def init_draft_statusbar(sb):
"""
this function initializes draft statusbar
"""
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
# SNAP WIDGET - init ----------------------------------------------------
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 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
draft_annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0)
# initializes scale widget
scale_widget.draft_scales = draft_scales
scaleLabel = QtGui.QPushButton("Scale")
scaleLabel.setObjectName("ScaleLabel")
scaleLabel.setFlat(True)
menu = QtGui.QMenu(scaleLabel)
gUnits = QtGui.QActionGroup(menu)
for u in draft_scales:
a = QtGui.QAction(gUnits)
a.setText(u)
menu.addAction(a)
scaleLabel.setMenu(menu)
gUnits.triggered.connect(_set_scale)
scale_label = scale_to_label(draft_annotation_scale)
scaleLabel.setText(scale_label)
tooltip = "Set the scale used by draft annotation tools"
scaleLabel.setToolTip(QT_TRANSLATE_NOOP("draft",tooltip))
scale_widget.addWidget(scaleLabel)
scale_widget.scaleLabel = scaleLabel
# 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()
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 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()
+22 -11
View File
@@ -57,6 +57,17 @@ def get_draft_array_commands():
return ["Draft_ArrayTools"]
def get_draft_small_commands():
"""Return a list with only some utilities."""
return ["Draft_Layer",
"Draft_WorkingPlaneProxy",
"Draft_ToggleDisplayMode",
"Draft_AddToGroup",
"Draft_SelectGroup",
"Draft_AddConstruction",
"Draft_Heal"]
def get_draft_modification_commands():
"""Return the modification commands list."""
lst = ["Draft_Move", "Draft_Rotate",
@@ -73,9 +84,9 @@ def get_draft_modification_commands():
"Draft_Upgrade", "Draft_Downgrade",
"Separator",
"Draft_WireToBSpline", "Draft_Draft2Sketch",
"Draft_Slope", "Draft_FlipDimension",
"Separator",
"Draft_Shape2DView", "Draft_Drawing",
"Draft_WorkingPlaneProxy"]
"Draft_Shape2DView", "Draft_Drawing"]
return lst
@@ -104,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):
+2 -1
View File
@@ -157,7 +157,8 @@ def get_param_type(param):
"SvgLinesBlack", "dxfStdSize", "showSnapBar",
"hideSnapBar", "alwaysShowGrid", "renderPolylineWidth",
"showPlaneTracker", "UsePartPrimitives",
"DiscretizeEllipses", "showUnit"):
"DiscretizeEllipses", "showUnit",
"Draft_array_fuse", "Draft_array_Link"):
return "bool"
elif param in ("color", "constructioncolor",
"snapcolor", "gridColor"):
@@ -0,0 +1,929 @@
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the Draft Dimensions view provider classes
"""
## @package dimension
# \ingroup DRAFT
# \brief This module provides the view provider code for Draft Dimensions.
import FreeCAD as App
import DraftVecUtils, DraftGeomUtils
from pivy import coin
from PySide.QtCore import QT_TRANSLATE_NOOP
import draftutils.utils as utils
import draftutils.gui_utils as gui_utils
from draftviewproviders.view_draft_annotation import ViewProviderDraftAnnotation
class ViewProviderDimensionBase(ViewProviderDraftAnnotation):
"""
A View Provider for the Draft Dimension object
This class is not used directly, but inherited by all dimension
view providers.
DIMENSION VIEW PROVIDER NOMENCLATURE:
| txt | e
----o--------------------------------o-----
| |
| | d
| |
a b c b a
a = DimOvershoot (vobj)
b = Arrows (vobj)
c = Dimline (obj)
d = ExtLines (vobj)
e = ExtOvershoot (vobj)
txt = label (vobj)
COIN OBJECT STRUCTURE:
vobj.node.color
.drawstyle
.lineswitch1.coords
.line
.marks
.marksDimOvershoot
.marksExtOvershoot
.label.textpos
.color
.font
.text
vobj.node3d.color
.drawstyle
.lineswitch3.coords
.line
.marks
.marksDimOvershoot
.marksExtOvershoot
.label3d.textpos
.color
.font3d
.text3d
"""
def __init__(self, vobj):
super(ViewProviderDimensionBase, self).__init__(vobj)
# text properties
vobj.addProperty("App::PropertyFont","FontName",
"Text",
QT_TRANSLATE_NOOP("App::Property","Font name"))
vobj.addProperty("App::PropertyLength","FontSize",
"Text",
QT_TRANSLATE_NOOP("App::Property","Font size"))
vobj.addProperty("App::PropertyLength","TextSpacing",
"Text",
QT_TRANSLATE_NOOP("App::Property",
"Spacing between text and dimension line"))
vobj.addProperty("App::PropertyBool","FlipText",
"Text",
QT_TRANSLATE_NOOP("App::Property",
"Rotate the dimension text 180 degrees"))
vobj.addProperty("App::PropertyVectorDistance","TextPosition",
"Text",
QT_TRANSLATE_NOOP("App::Property",
"Text Position. \n"
"Leave (0,0,0) for automatic position"))
vobj.addProperty("App::PropertyString","Override",
"Text",
QT_TRANSLATE_NOOP("App::Property",
"Text override. \n"
"Use $dim to insert the dimension length"))
# units properties
vobj.addProperty("App::PropertyInteger","Decimals",
"Units",
QT_TRANSLATE_NOOP("App::Property",
"The number of decimals to show"))
vobj.addProperty("App::PropertyBool","ShowUnit",
"Units",
QT_TRANSLATE_NOOP("App::Property",
"Show the unit suffix"))
vobj.addProperty("App::PropertyString","UnitOverride",
"Units",
QT_TRANSLATE_NOOP("App::Property",
"A unit to express the measurement. \n"
"Leave blank for system default"))
# graphics properties
vobj.addProperty("App::PropertyLength","ArrowSize",
"Graphics",
QT_TRANSLATE_NOOP("App::Property","Arrow size"))
vobj.addProperty("App::PropertyEnumeration","ArrowType",
"Graphics",
QT_TRANSLATE_NOOP("App::Property","Arrow type"))
vobj.addProperty("App::PropertyBool","FlipArrows",
"Graphics",
QT_TRANSLATE_NOOP("App::Property",
"Rotate the dimension arrows 180 degrees"))
vobj.addProperty("App::PropertyDistance","DimOvershoot",
"Graphics",
QT_TRANSLATE_NOOP("App::Property",
"The distance the dimension line is extended\n"
"past the extension lines"))
vobj.addProperty("App::PropertyDistance","ExtLines",
"Graphics",
QT_TRANSLATE_NOOP("App::Property",
"Length of the extension lines"))
vobj.addProperty("App::PropertyDistance","ExtOvershoot",
"Graphics",
QT_TRANSLATE_NOOP("App::Property",
"Length of the extension line \n"
"above the dimension line"))
vobj.addProperty("App::PropertyBool","ShowLine",
"Graphics",
QT_TRANSLATE_NOOP("App::Property",
"Shows the dimension line and arrows"))
vobj.FontSize = utils.get_param("textheight",0.20)
vobj.TextSpacing = utils.get_param("dimspacing",0.05)
vobj.FontName = utils.get_param("textfont","")
vobj.ArrowSize = utils.get_param("arrowsize",0.1)
vobj.ArrowType = utils.ARROW_TYPES
vobj.ArrowType = utils.ARROW_TYPES[utils.get_param("dimsymbol",0)]
vobj.ExtLines = utils.get_param("extlines",0.3)
vobj.DimOvershoot = utils.get_param("dimovershoot",0)
vobj.ExtOvershoot = utils.get_param("extovershoot",0)
vobj.Decimals = utils.get_param("dimPrecision",2)
vobj.ShowUnit = utils.get_param("showUnit",True)
vobj.ShowLine = True
def updateData(self, obj, prop):
"""called when the base object is changed"""
return
def onChanged(self, vobj, prop):
"""called when a view property has changed"""
return
def doubleClicked(self,vobj):
self.setEdit(vobj)
def getDisplayModes(self,vobj):
return ["2D","3D"]
def getDefaultDisplayMode(self):
if hasattr(self,"defaultmode"):
return self.defaultmode
else:
return ["2D","3D"][utils.get_param("dimstyle",0)]
def setDisplayMode(self,mode):
return mode
def getIcon(self):
if self.is_linked_to_circle():
return ":/icons/Draft_DimensionRadius.svg"
return ":/icons/Draft_Dimension_Tree.svg"
def __getstate__(self):
return self.Object.ViewObject.DisplayMode
def __setstate__(self,state):
if state:
self.defaultmode = state
self.setDisplayMode(state)
class ViewProviderLinearDimension(ViewProviderDimensionBase):
"""
A View Provider for the Draft Linear Dimension object
"""
def __init__(self, vobj):
super(ViewProviderLinearDimension, self).__init__(vobj)
self.Object = vobj.Object
vobj.Proxy = self
def attach(self, vobj):
'''Setup the scene sub-graph of the view provider'''
self.Object = vobj.Object
self.color = coin.SoBaseColor()
self.font = coin.SoFont()
self.font3d = coin.SoFont()
self.text = coin.SoAsciiText()
self.text3d = coin.SoText2()
self.text.string = "d" # some versions of coin crash if string is not set
self.text3d.string = "d"
self.textpos = coin.SoTransform()
self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER
label = coin.SoSeparator()
label.addChild(self.textpos)
label.addChild(self.color)
label.addChild(self.font)
label.addChild(self.text)
label3d = coin.SoSeparator()
label3d.addChild(self.textpos)
label3d.addChild(self.color)
label3d.addChild(self.font3d)
label3d.addChild(self.text3d)
self.coord1 = coin.SoCoordinate3()
self.trans1 = coin.SoTransform()
self.coord2 = coin.SoCoordinate3()
self.trans2 = coin.SoTransform()
self.transDimOvershoot1 = coin.SoTransform()
self.transDimOvershoot2 = coin.SoTransform()
self.transExtOvershoot1 = coin.SoTransform()
self.transExtOvershoot2 = coin.SoTransform()
self.marks = coin.SoSeparator()
self.marksDimOvershoot = coin.SoSeparator()
self.marksExtOvershoot = coin.SoSeparator()
self.drawstyle = coin.SoDrawStyle()
self.line = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
self.coords = coin.SoCoordinate3()
self.node = coin.SoGroup()
self.node.addChild(self.color)
self.node.addChild(self.drawstyle)
self.lineswitch2 = coin.SoSwitch()
self.lineswitch2.whichChild = -3
self.node.addChild(self.lineswitch2)
self.lineswitch2.addChild(self.coords)
self.lineswitch2.addChild(self.line)
self.lineswitch2.addChild(self.marks)
self.lineswitch2.addChild(self.marksDimOvershoot)
self.lineswitch2.addChild(self.marksExtOvershoot)
self.node.addChild(label)
self.node3d = coin.SoGroup()
self.node3d.addChild(self.color)
self.node3d.addChild(self.drawstyle)
self.lineswitch3 = coin.SoSwitch()
self.lineswitch3.whichChild = -3
self.node3d.addChild(self.lineswitch3)
self.lineswitch3.addChild(self.coords)
self.lineswitch3.addChild(self.line)
self.lineswitch3.addChild(self.marks)
self.lineswitch3.addChild(self.marksDimOvershoot)
self.lineswitch3.addChild(self.marksExtOvershoot)
self.node3d.addChild(label3d)
vobj.addDisplayMode(self.node,"2D")
vobj.addDisplayMode(self.node3d,"3D")
self.updateData(vobj.Object,"Start")
self.onChanged(vobj,"FontSize")
self.onChanged(vobj,"FontName")
self.onChanged(vobj,"ArrowType")
self.onChanged(vobj,"LineColor")
self.onChanged(vobj,"DimOvershoot")
self.onChanged(vobj,"ExtOvershoot")
def updateData(self, obj, prop):
"""called when the base object is changed"""
import DraftGui
if prop in ["Start","End","Dimline","Direction"]:
if obj.Start == obj.End:
return
if not hasattr(self,"node"):
return
import Part, DraftGeomUtils
from pivy import coin
# calculate the 4 points
self.p1 = obj.Start
self.p4 = obj.End
base = None
if hasattr(obj,"Direction"):
if not DraftVecUtils.isNull(obj.Direction):
v2 = self.p1.sub(obj.Dimline)
v3 = self.p4.sub(obj.Dimline)
v2 = DraftVecUtils.project(v2,obj.Direction)
v3 = DraftVecUtils.project(v3,obj.Direction)
self.p2 = obj.Dimline.add(v2)
self.p3 = obj.Dimline.add(v3)
if DraftVecUtils.equals(self.p2,self.p3):
base = None
proj = None
else:
base = Part.LineSegment(self.p2,self.p3).toShape()
proj = DraftGeomUtils.findDistance(self.p1,base)
if proj:
proj = proj.negative()
if not base:
if DraftVecUtils.equals(self.p1,self.p4):
base = None
proj = None
else:
base = Part.LineSegment(self.p1,self.p4).toShape()
proj = DraftGeomUtils.findDistance(obj.Dimline,base)
if proj:
self.p2 = self.p1.add(proj.negative())
self.p3 = self.p4.add(proj.negative())
else:
self.p2 = self.p1
self.p3 = self.p4
if proj:
if hasattr(obj.ViewObject,"ExtLines") and hasattr(obj.ViewObject,"ScaleMultiplier"):
dmax = obj.ViewObject.ExtLines.Value * obj.ViewObject.ScaleMultiplier
if dmax and (proj.Length > dmax):
if (dmax > 0):
self.p1 = self.p2.add(DraftVecUtils.scaleTo(proj,dmax))
self.p4 = self.p3.add(DraftVecUtils.scaleTo(proj,dmax))
else:
rest = proj.Length + dmax
self.p1 = self.p2.add(DraftVecUtils.scaleTo(proj,rest))
self.p4 = self.p3.add(DraftVecUtils.scaleTo(proj,rest))
else:
proj = (self.p3.sub(self.p2)).cross(App.Vector(0,0,1))
# calculate the arrows positions
self.trans1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
self.coord1.point.setValue((self.p2.x,self.p2.y,self.p2.z))
self.trans2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
self.coord2.point.setValue((self.p3.x,self.p3.y,self.p3.z))
# calculate dimension and extension lines overshoots positions
self.transDimOvershoot1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
self.transDimOvershoot2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
self.transExtOvershoot1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
self.transExtOvershoot2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
# calculate the text position and orientation
if hasattr(obj,"Normal"):
if DraftVecUtils.isNull(obj.Normal):
if proj:
norm = (self.p3.sub(self.p2).cross(proj)).negative()
else:
norm = App.Vector(0,0,1)
else:
norm = App.Vector(obj.Normal)
else:
if proj:
norm = (self.p3.sub(self.p2).cross(proj)).negative()
else:
norm = App.Vector(0,0,1)
if not DraftVecUtils.isNull(norm):
norm.normalize()
u = self.p3.sub(self.p2)
u.normalize()
v1 = norm.cross(u)
rot1 = App.Placement(DraftVecUtils.getPlaneRotation(u,v1,norm)).Rotation.Q
self.transDimOvershoot1.rotation.setValue((rot1[0],rot1[1],rot1[2],rot1[3]))
self.transDimOvershoot2.rotation.setValue((rot1[0],rot1[1],rot1[2],rot1[3]))
if hasattr(obj.ViewObject,"FlipArrows"):
if obj.ViewObject.FlipArrows:
u = u.negative()
v2 = norm.cross(u)
rot2 = App.Placement(DraftVecUtils.getPlaneRotation(u,v2,norm)).Rotation.Q
self.trans1.rotation.setValue((rot2[0],rot2[1],rot2[2],rot2[3]))
self.trans2.rotation.setValue((rot2[0],rot2[1],rot2[2],rot2[3]))
if self.p1 != self.p2:
u3 = self.p1.sub(self.p2)
u3.normalize()
v3 = norm.cross(u3)
rot3 = App.Placement(DraftVecUtils.getPlaneRotation(u3,v3,norm)).Rotation.Q
self.transExtOvershoot1.rotation.setValue((rot3[0],rot3[1],rot3[2],rot3[3]))
self.transExtOvershoot2.rotation.setValue((rot3[0],rot3[1],rot3[2],rot3[3]))
if hasattr(obj.ViewObject,"TextSpacing") and hasattr(obj.ViewObject,"ScaleMultiplier"):
ts = obj.ViewObject.TextSpacing.Value * obj.ViewObject.ScaleMultiplier
offset = DraftVecUtils.scaleTo(v1,ts)
else:
offset = DraftVecUtils.scaleTo(v1,0.05)
rott = rot1
if hasattr(obj.ViewObject,"FlipText"):
if obj.ViewObject.FlipText:
rott = App.Rotation(*rott).multiply(App.Rotation(norm,180)).Q
offset = offset.negative()
# setting text
try:
m = obj.ViewObject.DisplayMode
except: # swallow all exceptions here since it always fails on first run (Displaymode enum no set yet)
m = ["2D","3D"][utils.get_param("dimstyle",0)]
if m == "3D":
offset = offset.negative()
self.tbase = (self.p2.add((self.p3.sub(self.p2).multiply(0.5)))).add(offset)
if hasattr(obj.ViewObject,"TextPosition"):
if not DraftVecUtils.isNull(obj.ViewObject.TextPosition):
self.tbase = obj.ViewObject.TextPosition
self.textpos.translation.setValue([self.tbase.x,self.tbase.y,self.tbase.z])
self.textpos.rotation = coin.SbRotation(rott[0],rott[1],rott[2],rott[3])
su = True
if hasattr(obj.ViewObject,"ShowUnit"):
su = obj.ViewObject.ShowUnit
# set text value
l = self.p3.sub(self.p2).Length
unit = None
if hasattr(obj.ViewObject,"UnitOverride"):
unit = obj.ViewObject.UnitOverride
# special representation if "Building US" scheme
if App.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("UserSchema",0) == 5:
s = App.Units.Quantity(l,App.Units.Length).UserString
self.string = s.replace("' ","'- ")
self.string = s.replace("+"," ")
elif hasattr(obj.ViewObject,"Decimals"):
self.string = DraftGui.displayExternal(l,obj.ViewObject.Decimals,'Length',su,unit)
else:
self.string = DraftGui.displayExternal(l,None,'Length',su,unit)
if hasattr(obj.ViewObject,"Override"):
if obj.ViewObject.Override:
self.string = obj.ViewObject.Override.replace("$dim",\
self.string)
self.text.string = self.text3d.string = utils.string_encode_coin(self.string)
# set the lines
if m == "3D":
# calculate the spacing of the text
textsize = (len(self.string)*obj.ViewObject.FontSize.Value)/4.0
spacing = ((self.p3.sub(self.p2)).Length/2.0) - textsize
self.p2a = self.p2.add(DraftVecUtils.scaleTo(self.p3.sub(self.p2),spacing))
self.p2b = self.p3.add(DraftVecUtils.scaleTo(self.p2.sub(self.p3),spacing))
self.coords.point.setValues([[self.p1.x,self.p1.y,self.p1.z],
[self.p2.x,self.p2.y,self.p2.z],
[self.p2a.x,self.p2a.y,self.p2a.z],
[self.p2b.x,self.p2b.y,self.p2b.z],
[self.p3.x,self.p3.y,self.p3.z],
[self.p4.x,self.p4.y,self.p4.z]])
#self.line.numVertices.setValues([3,3])
self.line.coordIndex.setValues(0,7,(0,1,2,-1,3,4,5))
else:
self.coords.point.setValues([[self.p1.x,self.p1.y,self.p1.z],
[self.p2.x,self.p2.y,self.p2.z],
[self.p3.x,self.p3.y,self.p3.z],
[self.p4.x,self.p4.y,self.p4.z]])
#self.line.numVertices.setValue(4)
self.line.coordIndex.setValues(0,4,(0,1,2,3))
def onChanged(self, vobj, prop):
"""called when a view property has changed"""
if prop == "ScaleMultiplier" and hasattr(vobj,"ScaleMultiplier"):
# update all dimension values
if hasattr(self,"font"):
self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier
if hasattr(self,"font3d"):
self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier
if hasattr(self,"node") and hasattr(self,"p2") and hasattr(vobj,"ArrowSize"):
self.remove_dim_arrows()
self.draw_dim_arrows(vobj)
if hasattr(vobj,"DimOvershoot"):
self.remove_dim_overshoot()
self.draw_dim_overshoot(vobj)
if hasattr(vobj,"ExtOvershoot"):
self.remove_ext_overshoot()
self.draw_ext_overshoot(vobj)
self.updateData(vobj.Object,"Start")
vobj.Object.touch()
elif (prop == "FontSize") and hasattr(vobj,"FontSize"):
if hasattr(self,"font") and hasattr(vobj,"ScaleMultiplier"):
self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier
if hasattr(self,"font3d") and hasattr(vobj,"ScaleMultiplier"):
self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier
vobj.Object.touch()
elif (prop == "FontName") and hasattr(vobj,"FontName"):
if hasattr(self,"font") and hasattr(self,"font3d"):
self.font.name = self.font3d.name = str(vobj.FontName)
vobj.Object.touch()
elif (prop == "LineColor") and hasattr(vobj,"LineColor"):
if hasattr(self,"color"):
c = vobj.LineColor
self.color.rgb.setValue(c[0],c[1],c[2])
elif (prop == "LineWidth") and hasattr(vobj,"LineWidth"):
if hasattr(self,"drawstyle"):
self.drawstyle.lineWidth = vobj.LineWidth
elif (prop in ["ArrowSize","ArrowType"]) and hasattr(vobj,"ArrowSize"):
if hasattr(self,"node") and hasattr(self,"p2"):
if hasattr(vobj,"ScaleMultiplier"):
self.remove_dim_arrows()
self.draw_dim_arrows(vobj)
vobj.Object.touch()
elif (prop == "DimOvershoot") and hasattr(vobj,"DimOvershoot"):
if hasattr(vobj,"ScaleMultiplier"):
self.remove_dim_overshoot()
self.draw_dim_overshoot(vobj)
vobj.Object.touch()
elif (prop == "ExtOvershoot") and hasattr(vobj,"ExtOvershoot"):
if hasattr(vobj,"ScaleMultiplier"):
self.remove_ext_overshoot()
self.draw_ext_overshoot(vobj)
vobj.Object.touch()
elif (prop == "ShowLine") and hasattr(vobj,"ShowLine"):
if vobj.ShowLine:
self.lineswitch2.whichChild = -3
self.lineswitch3.whichChild = -3
else:
self.lineswitch2.whichChild = -1
self.lineswitch3.whichChild = -1
else:
self.updateData(vobj.Object,"Start")
def remove_dim_arrows(self):
# remove existing nodes
self.node.removeChild(self.marks)
self.node3d.removeChild(self.marks)
def draw_dim_arrows(self, vobj):
from pivy import coin
if not hasattr(vobj,"ArrowType"):
return
if self.p3.x < self.p2.x:
inv = False
else:
inv = True
# set scale
symbol = utils.ARROW_TYPES.index(vobj.ArrowType)
s = vobj.ArrowSize.Value * vobj.ScaleMultiplier
self.trans1.scaleFactor.setValue((s,s,s))
self.trans2.scaleFactor.setValue((s,s,s))
# set new nodes
self.marks = coin.SoSeparator()
self.marks.addChild(self.color)
s1 = coin.SoSeparator()
if symbol == "Circle":
s1.addChild(self.coord1)
else:
s1.addChild(self.trans1)
s1.addChild(gui_utils.dim_symbol(symbol,invert=not(inv)))
self.marks.addChild(s1)
s2 = coin.SoSeparator()
if symbol == "Circle":
s2.addChild(self.coord2)
else:
s2.addChild(self.trans2)
s2.addChild(gui_utils.dim_symbol(symbol,invert=inv))
self.marks.addChild(s2)
self.node.insertChild(self.marks,2)
self.node3d.insertChild(self.marks,2)
def remove_dim_overshoot(self):
self.node.removeChild(self.marksDimOvershoot)
self.node3d.removeChild(self.marksDimOvershoot)
def draw_dim_overshoot(self, vobj):
from pivy import coin
# set scale
s = vobj.DimOvershoot.Value * vobj.ScaleMultiplier
self.transDimOvershoot1.scaleFactor.setValue((s,s,s))
self.transDimOvershoot2.scaleFactor.setValue((s,s,s))
# remove existing nodes
# set new nodes
self.marksDimOvershoot = coin.SoSeparator()
if vobj.DimOvershoot.Value:
self.marksDimOvershoot.addChild(self.color)
s1 = coin.SoSeparator()
s1.addChild(self.transDimOvershoot1)
s1.addChild(gui_utils.dimDash((-1,0,0),(0,0,0)))
self.marksDimOvershoot.addChild(s1)
s2 = coin.SoSeparator()
s2.addChild(self.transDimOvershoot2)
s2.addChild(gui_utils.dimDash((0,0,0),(1,0,0)))
self.marksDimOvershoot.addChild(s2)
self.node.insertChild(self.marksDimOvershoot,2)
self.node3d.insertChild(self.marksDimOvershoot,2)
def remove_ext_overshoot(self):
self.node.removeChild(self.marksExtOvershoot)
self.node3d.removeChild(self.marksExtOvershoot)
def draw_ext_overshoot(self, vobj):
from pivy import coin
# set scale
s = vobj.ExtOvershoot.Value * vobj.ScaleMultiplier
self.transExtOvershoot1.scaleFactor.setValue((s,s,s))
self.transExtOvershoot2.scaleFactor.setValue((s,s,s))
# set new nodes
self.marksExtOvershoot = coin.SoSeparator()
if vobj.ExtOvershoot.Value:
self.marksExtOvershoot.addChild(self.color)
s1 = coin.SoSeparator()
s1.addChild(self.transExtOvershoot1)
s1.addChild(gui_utils.dimDash((0,0,0),(-1,0,0)))
self.marksExtOvershoot.addChild(s1)
s2 = coin.SoSeparator()
s2.addChild(self.transExtOvershoot2)
s2.addChild(gui_utils.dimDash((0,0,0),(-1,0,0)))
self.marksExtOvershoot.addChild(s2)
self.node.insertChild(self.marksExtOvershoot,2)
self.node3d.insertChild(self.marksExtOvershoot,2)
def is_linked_to_circle(self):
_obj = self.Object
if _obj.LinkedGeometry and len(_obj.LinkedGeometry) == 1:
lobj = _obj.LinkedGeometry[0][0]
lsub = _obj.LinkedGeometry[0][1]
if len(lsub) == 1 and "Edge" in lsub[0]:
n = int(lsub[0][4:]) - 1
edge = lobj.Shape.Edges[n]
if DraftGeomUtils.geomType(edge) == "Circle":
return True
return False
def getIcon(self):
if self.is_linked_to_circle():
return ":/icons/Draft_DimensionRadius.svg"
return ":/icons/Draft_Dimension_Tree.svg"
class ViewProviderAngularDimension(ViewProviderDimensionBase):
"""A View Provider for the Draft Angular Dimension object"""
def __init__(self, vobj):
super(ViewProviderAngularDimension, self).__init__(vobj)
vobj.addProperty("App::PropertyBool","FlipArrows",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"Rotate the dimension arrows 180 degrees"))
self.Object = vobj.Object
vobj.Proxy = self
def attach(self, vobj):
'''Setup the scene sub-graph of the view provider'''
from pivy import coin
self.Object = vobj.Object
self.color = coin.SoBaseColor()
if hasattr(vobj,"LineColor"):
self.color.rgb.setValue(vobj.LineColor[0],vobj.LineColor[1],vobj.LineColor[2])
self.font = coin.SoFont()
self.font3d = coin.SoFont()
self.text = coin.SoAsciiText()
self.text3d = coin.SoText2()
self.text.string = "d" # some versions of coin crash if string is not set
self.text3d.string = "d"
self.text.justification = self.text3d.justification = coin.SoAsciiText.CENTER
self.textpos = coin.SoTransform()
label = coin.SoSeparator()
label.addChild(self.textpos)
label.addChild(self.color)
label.addChild(self.font)
label.addChild(self.text)
label3d = coin.SoSeparator()
label3d.addChild(self.textpos)
label3d.addChild(self.color)
label3d.addChild(self.font3d)
label3d.addChild(self.text3d)
self.coord1 = coin.SoCoordinate3()
self.trans1 = coin.SoTransform()
self.coord2 = coin.SoCoordinate3()
self.trans2 = coin.SoTransform()
self.marks = coin.SoSeparator()
self.drawstyle = coin.SoDrawStyle()
self.coords = coin.SoCoordinate3()
self.arc = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
self.node = coin.SoGroup()
self.node.addChild(self.color)
self.node.addChild(self.drawstyle)
self.node.addChild(self.coords)
self.node.addChild(self.arc)
self.node.addChild(self.marks)
self.node.addChild(label)
self.node3d = coin.SoGroup()
self.node3d.addChild(self.color)
self.node3d.addChild(self.drawstyle)
self.node3d.addChild(self.coords)
self.node3d.addChild(self.arc)
self.node3d.addChild(self.marks)
self.node3d.addChild(label3d)
vobj.addDisplayMode(self.node,"2D")
vobj.addDisplayMode(self.node3d,"3D")
self.updateData(vobj.Object,None)
self.onChanged(vobj,"FontSize")
self.onChanged(vobj,"FontName")
self.onChanged(vobj,"ArrowType")
self.onChanged(vobj,"LineColor")
def updateData(self, obj, prop):
if hasattr(self,"arc"):
from pivy import coin
import Part, DraftGeomUtils
import DraftGui
arcsegs = 24
# calculate the arc data
if DraftVecUtils.isNull(obj.Normal):
norm = App.Vector(0,0,1)
else:
norm = obj.Normal
radius = (obj.Dimline.sub(obj.Center)).Length
self.circle = Part.makeCircle(radius,obj.Center,norm,obj.FirstAngle.Value,obj.LastAngle.Value)
self.p2 = self.circle.Vertexes[0].Point
self.p3 = self.circle.Vertexes[-1].Point
mp = DraftGeomUtils.findMidpoint(self.circle.Edges[0])
ray = mp.sub(obj.Center)
# set text value
if obj.LastAngle.Value > obj.FirstAngle.Value:
a = obj.LastAngle.Value - obj.FirstAngle.Value
else:
a = (360 - obj.FirstAngle.Value) + obj.LastAngle.Value
su = True
if hasattr(obj.ViewObject,"ShowUnit"):
su = obj.ViewObject.ShowUnit
if hasattr(obj.ViewObject,"Decimals"):
self.string = DraftGui.displayExternal(a,obj.ViewObject.Decimals,'Angle',su)
else:
self.string = DraftGui.displayExternal(a,None,'Angle',su)
if obj.ViewObject.Override:
self.string = obj.ViewObject.Override.replace("$dim",\
self.string)
self.text.string = self.text3d.string = utils.string_encode_coin(self.string)
# check display mode
try:
m = obj.ViewObject.DisplayMode
except: # swallow all exceptions here since it always fails on first run (Displaymode enum no set yet)
m = ["2D","3D"][utils.get_param("dimstyle",0)]
# set the arc
if m == "3D":
# calculate the spacing of the text
spacing = (len(self.string)*obj.ViewObject.FontSize.Value)/8.0
pts1 = []
cut = None
pts2 = []
for i in range(arcsegs+1):
p = self.circle.valueAt(self.circle.FirstParameter+((self.circle.LastParameter-self.circle.FirstParameter)/arcsegs)*i)
if (p.sub(mp)).Length <= spacing:
if cut is None:
cut = i
else:
if cut is None:
pts1.append([p.x,p.y,p.z])
else:
pts2.append([p.x,p.y,p.z])
self.coords.point.setValues(pts1+pts2)
i1 = len(pts1)
i2 = i1+len(pts2)
self.arc.coordIndex.setValues(0,len(pts1)+len(pts2)+1,list(range(len(pts1)))+[-1]+list(range(i1,i2)))
if (len(pts1) >= 3) and (len(pts2) >= 3):
self.circle1 = Part.Arc(App.Vector(pts1[0][0],pts1[0][1],pts1[0][2]),App.Vector(pts1[1][0],pts1[1][1],pts1[1][2]),App.Vector(pts1[-1][0],pts1[-1][1],pts1[-1][2])).toShape()
self.circle2 = Part.Arc(App.Vector(pts2[0][0],pts2[0][1],pts2[0][2]),App.Vector(pts2[1][0],pts2[1][1],pts2[1][2]),App.Vector(pts2[-1][0],pts2[-1][1],pts2[-1][2])).toShape()
else:
pts = []
for i in range(arcsegs+1):
p = self.circle.valueAt(self.circle.FirstParameter+((self.circle.LastParameter-self.circle.FirstParameter)/arcsegs)*i)
pts.append([p.x,p.y,p.z])
self.coords.point.setValues(pts)
self.arc.coordIndex.setValues(0,arcsegs+1,list(range(arcsegs+1)))
# set the arrow coords and rotation
self.trans1.translation.setValue((self.p2.x,self.p2.y,self.p2.z))
self.coord1.point.setValue((self.p2.x,self.p2.y,self.p2.z))
self.trans2.translation.setValue((self.p3.x,self.p3.y,self.p3.z))
self.coord2.point.setValue((self.p3.x,self.p3.y,self.p3.z))
# calculate small chords to make arrows look better
arrowlength = 4*obj.ViewObject.ArrowSize.Value
u1 = (self.circle.valueAt(self.circle.FirstParameter+arrowlength)).sub(self.circle.valueAt(self.circle.FirstParameter)).normalize()
u2 = (self.circle.valueAt(self.circle.LastParameter)).sub(self.circle.valueAt(self.circle.LastParameter-arrowlength)).normalize()
if hasattr(obj.ViewObject,"FlipArrows"):
if obj.ViewObject.FlipArrows:
u1 = u1.negative()
u2 = u2.negative()
w2 = self.circle.Curve.Axis
w1 = w2.negative()
v1 = w1.cross(u1)
v2 = w2.cross(u2)
q1 = App.Placement(DraftVecUtils.getPlaneRotation(u1,v1,w1)).Rotation.Q
q2 = App.Placement(DraftVecUtils.getPlaneRotation(u2,v2,w2)).Rotation.Q
self.trans1.rotation.setValue((q1[0],q1[1],q1[2],q1[3]))
self.trans2.rotation.setValue((q2[0],q2[1],q2[2],q2[3]))
# setting text pos & rot
self.tbase = mp
if hasattr(obj.ViewObject,"TextPosition"):
if not DraftVecUtils.isNull(obj.ViewObject.TextPosition):
self.tbase = obj.ViewObject.TextPosition
u3 = ray.cross(norm).normalize()
v3 = norm.cross(u3)
r = App.Placement(DraftVecUtils.getPlaneRotation(u3,v3,norm)).Rotation
offset = r.multVec(App.Vector(0,1,0))
if hasattr(obj.ViewObject,"TextSpacing"):
offset = DraftVecUtils.scaleTo(offset,obj.ViewObject.TextSpacing.Value)
else:
offset = DraftVecUtils.scaleTo(offset,0.05)
if m == "3D":
offset = offset.negative()
self.tbase = self.tbase.add(offset)
q = r.Q
self.textpos.translation.setValue([self.tbase.x,self.tbase.y,self.tbase.z])
self.textpos.rotation = coin.SbRotation(q[0],q[1],q[2],q[3])
# set the angle property
if round(obj.Angle,utils.precision()) != round(a,utils.precision()):
obj.Angle = a
def onChanged(self, vobj, prop):
if prop == "ScaleMultiplier" and hasattr(vobj,"ScaleMultiplier"):
# update all dimension values
if hasattr(self,"font"):
self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier
if hasattr(self,"font3d"):
self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier
if hasattr(self,"node") and hasattr(self,"p2") and hasattr(vobj,"ArrowSize"):
self.remove_dim_arrows()
self.draw_dim_arrows(vobj)
self.updateData(vobj.Object,"Start")
vobj.Object.touch()
elif prop == "FontSize" and hasattr(vobj,"ScaleMultiplier"):
if hasattr(self,"font"):
self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier
if hasattr(self,"font3d"):
self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier
vobj.Object.touch()
elif prop == "FontName":
if hasattr(self,"font") and hasattr(self,"font3d"):
self.font.name = self.font3d.name = str(vobj.FontName)
vobj.Object.touch()
elif prop == "LineColor":
if hasattr(self,"color") and hasattr(vobj,"LineColor"):
c = vobj.LineColor
self.color.rgb.setValue(c[0],c[1],c[2])
elif prop == "LineWidth":
if hasattr(self,"drawstyle"):
self.drawstyle.lineWidth = vobj.LineWidth
elif prop in ["ArrowSize","ArrowType"] and hasattr(vobj,"ScaleMultiplier"):
if hasattr(self,"node") and hasattr(self,"p2"):
self.remove_dim_arrows()
self.draw_dim_arrows(vobj)
vobj.Object.touch()
else:
self.updateData(vobj.Object, None)
def remove_dim_arrows(self):
# remove existing nodes
self.node.removeChild(self.marks)
self.node3d.removeChild(self.marks)
def draw_dim_arrows(self, vobj):
from pivy import coin
if not hasattr(vobj,"ArrowType"):
return
# set scale
symbol = utils.ARROW_TYPES.index(vobj.ArrowType)
s = vobj.ArrowSize.Value * vobj.ScaleMultiplier
self.trans1.scaleFactor.setValue((s,s,s))
self.trans2.scaleFactor.setValue((s,s,s))
# set new nodes
self.marks = coin.SoSeparator()
self.marks.addChild(self.color)
s1 = coin.SoSeparator()
if symbol == "Circle":
s1.addChild(self.coord1)
else:
s1.addChild(self.trans1)
s1.addChild(gui_utils.dim_symbol(symbol,invert=False))
self.marks.addChild(s1)
s2 = coin.SoSeparator()
if symbol == "Circle":
s2.addChild(self.coord2)
else:
s2.addChild(self.trans2)
s2.addChild(gui_utils.dim_symbol(symbol,invert=True))
self.marks.addChild(s2)
self.node.insertChild(self.marks,2)
self.node3d.insertChild(self.marks,2)
def getIcon(self):
return ":/icons/Draft_DimensionAngular.svg"
@@ -0,0 +1,118 @@
# ***************************************************************************
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the Draft Annotations view provider base class
"""
## @package annotation
# \ingroup DRAFT
# \brief This module provides the Draft Annotations view provider base class
import FreeCAD as App
from PySide.QtCore import QT_TRANSLATE_NOOP
if App.GuiUp:
import FreeCADGui as Gui
class ViewProviderDraftAnnotation(object):
"""
The base class for Draft Annotation Viewproviders
This class is not used directly, but inherited by all annotation
view providers.
"""
def __init__(self, vobj):
#vobj.Proxy = self
#self.Object = vobj.Object
# annotation properties
vobj.addProperty("App::PropertyFloat","ScaleMultiplier",
"Annotation",QT_TRANSLATE_NOOP("App::Property",
"Dimension size overall multiplier"))
# graphics properties
vobj.addProperty("App::PropertyFloat","LineWidth",
"Graphics",QT_TRANSLATE_NOOP("App::Property","Line width"))
vobj.addProperty("App::PropertyColor","LineColor",
"Graphics",QT_TRANSLATE_NOOP("App::Property","Line color"))
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0)
if annotation_scale != 0:
vobj.ScaleMultiplier = 1 / annotation_scale
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def attach(self,vobj):
self.Object = vobj.Object
return
def updateData(self, obj, prop):
return
def getDisplayModes(self, vobj):
modes=[]
return modes
def setDisplayMode(self, mode):
return mode
def onChanged(self, vobj, prop):
return
def execute(self,vobj):
return
def setEdit(self,vobj,mode=0):
if mode == 0:
Gui.runCommand("Draft_Edit")
return True
return False
def unsetEdit(self,vobj,mode=0):
if App.activeDraftCommand:
App.activeDraftCommand.finish()
Gui.Control.closeDialog()
return False
def getIcon(self):
return ":/icons/Draft_Draft.svg"
def claimChildren(self):
"""perhaps this is not useful???"""
objs = []
if hasattr(self.Object,"Base"):
objs.append(self.Object.Base)
if hasattr(self.Object,"Objects"):
objs.extend(self.Object.Objects)
if hasattr(self.Object,"Components"):
objs.extend(self.Object.Components)
if hasattr(self.Object,"Group"):
objs.extend(self.Object.Group)
return objs
@@ -0,0 +1,322 @@
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the Draft Label view provider classes
"""
## @package label
# \ingroup DRAFT
# \brief This module provides the view provider code for Draft Label.
import FreeCAD as App
import DraftVecUtils, DraftGeomUtils
import math, sys
from pivy import coin
from PySide.QtCore import QT_TRANSLATE_NOOP
import draftutils.utils as utils
import draftutils.gui_utils as gui_utils
from draftviewproviders.view_draft_annotation import ViewProviderDraftAnnotation
if App.GuiUp:
import FreeCADGui as Gui
class ViewProviderLabel(ViewProviderDraftAnnotation):
"""A View Provider for the Label annotation object"""
def __init__(self,vobj):
super(ViewProviderLabel, self).__init__(vobj)
self.set_properties(vobj)
self.Object = vobj.Object
vobj.Proxy = self
def set_properties(self, vobj):
# Text properties
vobj.addProperty("App::PropertyLength","TextSize",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The size of the text"))
vobj.addProperty("App::PropertyFont","TextFont",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The font of the text"))
vobj.addProperty("App::PropertyEnumeration","TextAlignment",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The vertical alignment of the text"))
vobj.addProperty("App::PropertyColor","TextColor",
"Text",QT_TRANSLATE_NOOP("App::Property",
"Text color"))
vobj.addProperty("App::PropertyInteger","MaxChars",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The maximum number of characters on each line of the text box"))
# Graphics properties
vobj.addProperty("App::PropertyLength","ArrowSize",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"The size of the arrow"))
vobj.addProperty("App::PropertyEnumeration","ArrowType",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"The type of arrow of this label"))
vobj.addProperty("App::PropertyEnumeration","Frame",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"The type of frame around the text of this object"))
vobj.addProperty("App::PropertyBool","Line",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"Display a leader line or not"))
vobj.addProperty("App::PropertyFloat","LineWidth",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"Line width"))
vobj.addProperty("App::PropertyColor","LineColor",
"Graphics",QT_TRANSLATE_NOOP("App::Property",
"Line color"))
self.Object = vobj.Object
vobj.TextAlignment = ["Top","Middle","Bottom"]
vobj.TextAlignment = "Middle"
vobj.LineWidth = utils.get_param("linewidth",1)
vobj.TextFont = utils.get_param("textfont")
vobj.TextSize = utils.get_param("textheight",1)
vobj.ArrowSize = utils.get_param("arrowsize",1)
vobj.ArrowType = utils.ARROW_TYPES
vobj.ArrowType = utils.ARROW_TYPES[utils.get_param("dimsymbol")]
vobj.Frame = ["None","Rectangle"]
vobj.Line = True
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0)
vobj.ScaleMultiplier = 1 / annotation_scale
def getIcon(self):
return ":/icons/Draft_Label.svg"
def claimChildren(self):
return []
def attach(self,vobj):
'''Setup the scene sub-graph of the view provider'''
self.arrow = coin.SoSeparator()
self.arrowpos = coin.SoTransform()
self.arrow.addChild(self.arrowpos)
self.matline = coin.SoMaterial()
self.drawstyle = coin.SoDrawStyle()
self.drawstyle.style = coin.SoDrawStyle.LINES
self.lcoords = coin.SoCoordinate3()
self.line = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
self.mattext = coin.SoMaterial()
textdrawstyle = coin.SoDrawStyle()
textdrawstyle.style = coin.SoDrawStyle.FILLED
self.textpos = coin.SoTransform()
self.font = coin.SoFont()
self.text2d = coin.SoText2()
self.text3d = coin.SoAsciiText()
self.text2d.string = self.text3d.string = "Label" # need to init with something, otherwise, crash!
self.text2d.justification = coin.SoText2.RIGHT
self.text3d.justification = coin.SoAsciiText.RIGHT
self.fcoords = coin.SoCoordinate3()
self.frame = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
self.lineswitch = coin.SoSwitch()
switchnode = coin.SoSeparator()
switchnode.addChild(self.line)
switchnode.addChild(self.arrow)
self.lineswitch.addChild(switchnode)
self.lineswitch.whichChild = 0
self.node2d = coin.SoGroup()
self.node2d.addChild(self.matline)
self.node2d.addChild(self.arrow)
self.node2d.addChild(self.drawstyle)
self.node2d.addChild(self.lcoords)
self.node2d.addChild(self.lineswitch)
self.node2d.addChild(self.mattext)
self.node2d.addChild(textdrawstyle)
self.node2d.addChild(self.textpos)
self.node2d.addChild(self.font)
self.node2d.addChild(self.text2d)
self.node2d.addChild(self.fcoords)
self.node2d.addChild(self.frame)
self.node3d = coin.SoGroup()
self.node3d.addChild(self.matline)
self.node3d.addChild(self.arrow)
self.node3d.addChild(self.drawstyle)
self.node3d.addChild(self.lcoords)
self.node3d.addChild(self.lineswitch)
self.node3d.addChild(self.mattext)
self.node3d.addChild(textdrawstyle)
self.node3d.addChild(self.textpos)
self.node3d.addChild(self.font)
self.node3d.addChild(self.text3d)
self.node3d.addChild(self.fcoords)
self.node3d.addChild(self.frame)
vobj.addDisplayMode(self.node2d,"2D text")
vobj.addDisplayMode(self.node3d,"3D text")
self.onChanged(vobj,"LineColor")
self.onChanged(vobj,"TextColor")
self.onChanged(vobj,"ArrowSize")
self.onChanged(vobj,"Line")
def getDisplayModes(self,vobj):
return ["2D text","3D text"]
def getDefaultDisplayMode(self):
return "3D text"
def setDisplayMode(self,mode):
return mode
def updateData(self,obj,prop):
if prop == "Points":
from pivy import coin
if len(obj.Points) >= 2:
self.line.coordIndex.deleteValues(0)
self.lcoords.point.setValues(obj.Points)
self.line.coordIndex.setValues(0,len(obj.Points),range(len(obj.Points)))
self.onChanged(obj.ViewObject,"TextSize")
self.onChanged(obj.ViewObject,"ArrowType")
if obj.StraightDistance > 0:
self.text2d.justification = coin.SoText2.RIGHT
self.text3d.justification = coin.SoAsciiText.RIGHT
else:
self.text2d.justification = coin.SoText2.LEFT
self.text3d.justification = coin.SoAsciiText.LEFT
elif prop == "Text":
if obj.Text:
if sys.version_info.major >= 3:
self.text2d.string.setValues([l for l in obj.Text if l])
self.text3d.string.setValues([l for l in obj.Text if l])
else:
self.text2d.string.setValues([l.encode("utf8") for l in obj.Text if l])
self.text3d.string.setValues([l.encode("utf8") for l in obj.Text if l])
self.onChanged(obj.ViewObject,"TextAlignment")
def getTextSize(self,vobj):
if vobj.DisplayMode == "3D text":
text = self.text3d
else:
text = self.text2d
v = Gui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion()
b = coin.SoGetBoundingBoxAction(v)
text.getBoundingBox(b)
return b.getBoundingBox().getSize().getValue()
def onChanged(self,vobj,prop):
if prop == "ScaleMultiplier":
if not hasattr(vobj,"ScaleMultiplier"):
return
if hasattr(vobj,"TextSize") and hasattr(vobj,"TextAlignment"):
self.update_label(vobj)
if hasattr(vobj,"ArrowSize"):
s = vobj.ArrowSize.Value * vobj.ScaleMultiplier
if s:
self.arrowpos.scaleFactor.setValue((s,s,s))
elif prop == "LineColor":
if hasattr(vobj,"LineColor"):
l = vobj.LineColor
self.matline.diffuseColor.setValue([l[0],l[1],l[2]])
elif prop == "TextColor":
if hasattr(vobj,"TextColor"):
l = vobj.TextColor
self.mattext.diffuseColor.setValue([l[0],l[1],l[2]])
elif prop == "LineWidth":
if hasattr(vobj,"LineWidth"):
self.drawstyle.lineWidth = vobj.LineWidth
elif (prop == "TextFont"):
if hasattr(vobj,"TextFont"):
self.font.name = vobj.TextFont.encode("utf8")
elif prop in ["TextSize","TextAlignment"] and hasattr(vobj,"ScaleMultiplier"):
if hasattr(vobj,"TextSize") and hasattr(vobj,"TextAlignment"):
self.update_label(vobj)
elif prop == "Line":
if hasattr(vobj,"Line"):
if vobj.Line:
self.lineswitch.whichChild = 0
else:
self.lineswitch.whichChild = -1
elif prop == "ArrowType":
if hasattr(vobj,"ArrowType"):
if len(vobj.Object.Points) > 1:
if hasattr(self,"symbol"):
if self.arrow.findChild(self.symbol) != -1:
self.arrow.removeChild(self.symbol)
s = utils.ARROW_TYPES.index(vobj.ArrowType)
self.symbol = gui_utils.dim_symbol(s)
self.arrow.addChild(self.symbol)
self.arrowpos.translation.setValue(vobj.Object.Points[-1])
v1 = vobj.Object.Points[-2].sub(vobj.Object.Points[-1])
if not DraftVecUtils.isNull(v1):
v1.normalize()
v2 = App.Vector(0,0,1)
if round(v2.getAngle(v1),4) in [0,round(math.pi,4)]:
v2 = App.Vector(0,1,0)
v3 = v1.cross(v2).negative()
q = App.Placement(DraftVecUtils.getPlaneRotation(v1,v3,v2)).Rotation.Q
self.arrowpos.rotation.setValue((q[0],q[1],q[2],q[3]))
elif prop == "ArrowSize":
if hasattr(vobj,"ArrowSize") and hasattr(vobj,"ScaleMultiplier"):
s = vobj.ArrowSize.Value * vobj.ScaleMultiplier
if s:
self.arrowpos.scaleFactor.setValue((s,s,s))
elif prop == "Frame":
if hasattr(vobj,"Frame"):
self.frame.coordIndex.deleteValues(0)
if vobj.Frame == "Rectangle":
tsize = self.getTextSize(vobj)
pts = []
base = vobj.Object.Placement.Base.sub(App.Vector(self.textpos.translation.getValue().getValue()))
pts.append(base.add(App.Vector(0,tsize[1]*3,0)))
pts.append(pts[-1].add(App.Vector(-tsize[0]*6,0,0)))
pts.append(pts[-1].add(App.Vector(0,-tsize[1]*6,0)))
pts.append(pts[-1].add(App.Vector(tsize[0]*6,0,0)))
pts.append(pts[0])
self.fcoords.point.setValues(pts)
self.frame.coordIndex.setValues(0,len(pts),range(len(pts)))
def update_label(self, vobj):
self.font.size = vobj.TextSize.Value * vobj.ScaleMultiplier
v = App.Vector(1,0,0)
if vobj.Object.StraightDistance > 0:
v = v.negative()
v.multiply(vobj.TextSize/10)
tsize = self.getTextSize(vobj)
if (tsize is not None) and (len(vobj.Object.Text) > 1):
v = v.add(App.Vector(0,(tsize[1]-1)*2,0))
if vobj.TextAlignment == "Top":
v = v.add(App.Vector(0,-tsize[1]*2,0))
elif vobj.TextAlignment == "Middle":
v = v.add(App.Vector(0,-tsize[1],0))
v = vobj.Object.Placement.Rotation.multVec(v)
pos = vobj.Object.Placement.Base.add(v)
self.textpos.translation.setValue(pos)
self.textpos.rotation.setValue(vobj.Object.Placement.Rotation.Q)
@@ -0,0 +1,180 @@
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <[email protected]> *
# * Copyright (c) 2009, 2010 Ken Cline <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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. *
# * *
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""This module provides the Draft Text view provider classes
"""
## @package text
# \ingroup DRAFT
# \brief This module provides the view provider code for Draft Text.
import FreeCAD as App
import DraftVecUtils, DraftGeomUtils
import math, sys
from pivy import coin
from PySide.QtCore import QT_TRANSLATE_NOOP
import draftutils.utils as utils
import draftutils.gui_utils as gui_utils
from draftviewproviders.view_draft_annotation import ViewProviderDraftAnnotation
class ViewProviderText(ViewProviderDraftAnnotation):
"""A View Provider for the Draft Text annotation"""
def __init__(self,vobj):
super(ViewProviderText, self).__init__(vobj)
self.set_properties(vobj)
self.Object = vobj.Object
vobj.Proxy = self
def set_properties(self, vobj):
vobj.addProperty("App::PropertyFloat","ScaleMultiplier",
"Annotation",QT_TRANSLATE_NOOP("App::Property",
"Dimension size overall multiplier"))
vobj.addProperty("App::PropertyLength","FontSize",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The size of the text"))
vobj.addProperty("App::PropertyFont","FontName",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The font of the text"))
vobj.addProperty("App::PropertyEnumeration","Justification",
"Text",QT_TRANSLATE_NOOP("App::Property",
"The vertical alignment of the text"))
vobj.addProperty("App::PropertyColor","TextColor",
"Text",QT_TRANSLATE_NOOP("App::Property",
"Text color"))
vobj.addProperty("App::PropertyFloat","LineSpacing",
"Text",QT_TRANSLATE_NOOP("App::Property",
"Line spacing (relative to font size)"))
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0)
vobj.ScaleMultiplier = 1 / annotation_scale
vobj.Justification = ["Left","Center","Right"]
vobj.FontName = utils.get_param("textfont","sans")
vobj.FontSize = utils.get_param("textheight",1)
def getIcon(self):
return ":/icons/Draft_Text.svg"
def claimChildren(self):
return []
def attach(self,vobj):
'''Setup the scene sub-graph of the view provider'''
self.mattext = coin.SoMaterial()
textdrawstyle = coin.SoDrawStyle()
textdrawstyle.style = coin.SoDrawStyle.FILLED
self.trans = coin.SoTransform()
self.font = coin.SoFont()
self.text2d = coin.SoAsciiText()
self.text3d = coin.SoText2()
self.text2d.string = self.text3d.string = "Label" # need to init with something, otherwise, crash!
self.text2d.justification = coin.SoAsciiText.LEFT
self.text3d.justification = coin.SoText2.LEFT
self.node2d = coin.SoGroup()
self.node2d.addChild(self.trans)
self.node2d.addChild(self.mattext)
self.node2d.addChild(textdrawstyle)
self.node2d.addChild(self.font)
self.node2d.addChild(self.text2d)
self.node3d = coin.SoGroup()
self.node3d.addChild(self.trans)
self.node3d.addChild(self.mattext)
self.node3d.addChild(textdrawstyle)
self.node3d.addChild(self.font)
self.node3d.addChild(self.text3d)
vobj.addDisplayMode(self.node2d,"2D text")
vobj.addDisplayMode(self.node3d,"3D text")
self.onChanged(vobj,"TextColor")
self.onChanged(vobj,"FontSize")
self.onChanged(vobj,"FontName")
self.onChanged(vobj,"Justification")
self.onChanged(vobj,"LineSpacing")
def getDisplayModes(self,vobj):
return ["2D text","3D text"]
def setDisplayMode(self,mode):
return mode
def updateData(self,obj,prop):
if prop == "Text":
if obj.Text:
if sys.version_info.major >= 3:
self.text2d.string.setValues([l for l in obj.Text if l])
self.text3d.string.setValues([l for l in obj.Text if l])
else:
self.text2d.string.setValues([l.encode("utf8") for l in obj.Text if l])
self.text3d.string.setValues([l.encode("utf8") for l in obj.Text if l])
elif prop == "Placement":
self.trans.translation.setValue(obj.Placement.Base)
self.trans.rotation.setValue(obj.Placement.Rotation.Q)
def onChanged(self,vobj,prop):
if prop == "ScaleMultiplier":
if "ScaleMultiplier" in vobj.PropertiesList and "FontSize" in vobj.PropertiesList:
self.font.size = vobj.FontSize.Value * vobj.ScaleMultiplier
elif prop == "TextColor":
if "TextColor" in vobj.PropertiesList:
l = vobj.TextColor
self.mattext.diffuseColor.setValue([l[0],l[1],l[2]])
elif (prop == "FontName"):
if "FontName" in vobj.PropertiesList:
self.font.name = vobj.FontName.encode("utf8")
elif prop == "FontSize":
if "FontSize" in vobj.PropertiesList and "ScaleMultiplier" in vobj.PropertiesList:
self.font.size = vobj.FontSize.Value * vobj.ScaleMultiplier
elif prop == "Justification":
try:
if getattr(vobj, "Justification", None) is not None:
if vobj.Justification == "Left":
self.text2d.justification = coin.SoAsciiText.LEFT
self.text3d.justification = coin.SoText2.LEFT
elif vobj.Justification == "Right":
self.text2d.justification = coin.SoAsciiText.RIGHT
self.text3d.justification = coin.SoText2.RIGHT
else:
self.text2d.justification = coin.SoAsciiText.CENTER
self.text3d.justification = coin.SoText2.CENTER
except AssertionError:
pass # Race condition - Justification enum has not been set yet
elif prop == "LineSpacing":
if "LineSpacing" in vobj.PropertiesList:
self.text2d.spacing = vobj.LineSpacing
self.text3d.spacing = vobj.LineSpacing
+1 -1
View File
@@ -261,7 +261,7 @@ def convertToDwg(dxffilename, dwgfilename):
import shutil
if shutil.which("dxf2dwg"):
proc = subprocess.Popen(("dxf2dwg", dxffilename, "-o", dwgfilename))
proc = subprocess.Popen(("dxf2dwg", dxffilename, "-y", "-o", dwgfilename))
proc.communicate()
return dwgfilename
+1 -1
View File
@@ -4169,7 +4169,7 @@ def readPreferences():
# reading parameters
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
if FreeCAD.GuiUp and p.GetBool("dxfShowDialog", False):
FreeCADGui.showPreferences("Import-Export", 2)
FreeCADGui.showPreferences("Import-Export", 3)
global dxfCreatePart, dxfCreateDraft, dxfCreateSketch
global dxfDiscretizeCurves, dxfStarBlocks
global dxfMakeBlocks, dxfJoin, dxfRenderPolylineWidth
+1
View File
@@ -72,6 +72,7 @@ SET(FemInOut_SRCS
feminout/importCcxFrdResults.py
feminout/importFenicsMesh.py
feminout/importInpMesh.py
feminout/importPyMesh.py
feminout/importToolsFem.py
feminout/importVTKResults.py
feminout/importYamlJsonMesh.py
+2
View File
@@ -27,6 +27,8 @@
import FreeCAD
FreeCAD.addExportType("FEM mesh Python (*.meshpy)", "feminout.importPyMesh")
FreeCAD.addExportType("FEM mesh TetGen (*.poly)", "feminout.convert2TetGen")
# see FemMesh::read() and FemMesh::write() methods in src/Mod/Fem/App/FemMesh.cpp
+2 -2
View File
@@ -67,7 +67,7 @@ def run_analysis(doc, base_name, filepath=""):
# print([obj.Name for obj in doc.Objects])
# filepath
if filepath is "":
if filepath == "":
filepath = join(gettmp(), "FEM_examples")
if not exists(filepath):
makedirs(filepath)
@@ -78,7 +78,7 @@ def run_analysis(doc, base_name, filepath=""):
from femtools.femutils import is_derived_from
if (
is_derived_from(m, "Fem::FemSolverObjectPython")
and m.Proxy.Type is not "Fem::FemSolverCalculixCcxTools"
and m.Proxy.Type != "Fem::FemSolverCalculixCcxTools"
):
solver = m
break
@@ -703,6 +703,8 @@ def get_displacement_scale_factor(res_obj):
z_span = abs(p_z_max - p_z_min)
span = max(x_span, y_span, z_span)
max_disp = max(x_max, y_max, z_max)
if max_disp == 0.0:
return 0.0 # avoid float division by zero
# FIXME - add max_allowed_disp to Preferences
max_allowed_disp = 0.01 * span
scale = max_allowed_disp / max_disp
+1 -1
View File
@@ -186,7 +186,7 @@ def export(objectslist, fileString, group_values_dict_nogui=None):
writeFenicsXML.write_fenics_mesh_xml(obj, fileString)
elif fileExtension.lower() == ".xdmf":
mesh_groups = importToolsFem.get_FemMeshObjectMeshGroups(obj)
if mesh_groups is not ():
if mesh_groups != ():
# if there are groups found, make task panel available if GuiUp
if FreeCAD.GuiUp == 1:
panel = WriteXDMFTaskPanel(obj, fileString)
+148
View File
@@ -0,0 +1,148 @@
# ***************************************************************************
# * Copyright (c) 2016 Bernd Hahnebach <[email protected]> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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 *
# * *
# ***************************************************************************
__title__ = "FreeCAD Python Mesh reader and writer"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
## @package importPyMesh
# \ingroup FEM
# \brief FreeCAD Python Mesh reader and writer for FEM workbench
import FreeCAD
from femmesh import meshtools
# ************************************************************************************************
# ********* generic FreeCAD import and export methods ********************************************
# names are fix given from FreeCAD, these methods are called from FreeCAD
# they are set in FEM modules Init.py
if open.__module__ == "__builtin__":
# because we'll redefine open below (Python2)
pyopen = open
elif open.__module__ == "io":
# because we'll redefine open below (Python3)
pyopen = open
# export mesh to python
def export(
objectslist,
filename
):
"called when freecad exports a file"
if len(objectslist) != 1:
FreeCAD.Console.PrintError("This exporter can only export one object.\n")
return
obj = objectslist[0]
if not obj.isDerivedFrom("Fem::FemMeshObject"):
FreeCAD.Console.PrintError("No FEM mesh object selected.\n")
return
femnodes_mesh = obj.FemMesh.Nodes
femelement_table = meshtools.get_femelement_table(obj.FemMesh)
if meshtools.is_solid_femmesh(obj.FemMesh):
fem_mesh_type = "Solid"
elif meshtools.is_face_femmesh(obj.FemMesh):
fem_mesh_type = "Face"
elif meshtools.is_edge_femmesh(obj.FemMesh):
fem_mesh_type = "Edge"
else:
FreeCAD.Console.PrintError("Export of this FEM mesh to Python not supported.\n")
return
f = pyopen(filename, "w")
write_python_mesh_to_file(femnodes_mesh, femelement_table, fem_mesh_type, f)
f.close()
# ************************************************************************************************
# ********* module specific methods **************************************************************
# writer:
# - a method directly writes a FemMesh to the mesh file
# - a method takes a file handle, mesh data and writes to the file handle
# ********* writer *******************************************************************************
def write(
fem_mesh,
filename
):
"""directly write a FemMesh to a Python mesh file
fem_mesh: a FemMesh"""
if not fem_mesh.isDerivedFrom("Fem::FemMesh"):
FreeCAD.Console.PrintError("Not a FemMesh was given as parameter.\n")
return
femnodes_mesh = fem_mesh.Nodes
femelement_table = meshtools.get_femelement_table(fem_mesh)
if meshtools.is_solid_femmesh(fem_mesh):
fem_mesh_type = "Solid"
elif meshtools.is_face_femmesh(fem_mesh):
fem_mesh_type = "Face"
elif meshtools.is_edge_femmesh(fem_mesh):
fem_mesh_type = "Edge"
else:
FreeCAD.Console.PrintError("Export of this FEM mesh to Python not supported.\n")
return
f = pyopen(filename, "w")
write_python_mesh_to_file(femnodes_mesh, femelement_table, fem_mesh_type, f)
f.close()
def write_python_mesh_to_file(femnodes_mesh, femelement_table, fem_mesh_type, f):
mesh_name = "femmesh"
# nodes
f.write("def create_nodes(femmesh):\n")
f.write(" # nodes\n")
for node in femnodes_mesh:
# print(node, ' --> ', femnodes_mesh[node])
vec = femnodes_mesh[node]
f.write(
" {0}.addNode({1}, {2}, {3}, {4})\n"
.format(mesh_name, vec.x, vec.y, vec.z, node)
)
f.write(" return True\n")
f.write("\n\n")
# elements
f.write("def create_elements(femmesh):\n")
f.write(" # elements\n")
for element in femelement_table:
# print(element, ' --> ', femelement_table[element])
if fem_mesh_type == "Solid":
f.write(
" {0}.addVolume({1}, {2})\n"
.format(mesh_name, list(femelement_table[element]), element)
)
elif fem_mesh_type == "Face":
f.write(
" {0}.addFace({1}, {2})\n"
.format(mesh_name, list(femelement_table[element]), element)
)
elif fem_mesh_type == "Edge":
f.write(
" {0}.addEdge({1}, {2})\n"
.format(mesh_name, list(femelement_table[element]), element)
)
f.write(" return True\n")
+1 -1
View File
@@ -317,7 +317,7 @@ def write_fenics_mesh_xdmf(
fem_mesh = fem_mesh_obj.FemMesh
gmshgroups = get_FemMeshObjectMeshGroups(fem_mesh_obj)
if gmshgroups is not ():
if gmshgroups != ():
Console.PrintMessage("found mesh groups\n")
for g in gmshgroups:
+49 -32
View File
@@ -1426,6 +1426,7 @@ def build_mesh_faces_of_volume_elements(
FreeCAD.Console.PrintLog("VolElement: {}\n".format(veID))
vol_node_ct = len(femelement_table[veID])
face_node_indexs = sorted(face_nodenumber_table[veID])
node_numbers = ()
if vol_node_ct == 10:
FreeCAD.Console.PrintLog(" --> tetra10 --> tria6 face\n")
# node order of face in tetra10 volume element
@@ -1441,7 +1442,7 @@ def build_mesh_faces_of_volume_elements(
else:
FreeCAD.Console.PrintError(
"Error in build_mesh_faces_of_volume_elements(): "
"hexa20: face not found! {}\n"
"tetra10: face not found! {}\n"
.format(face_node_indexs)
)
elif vol_node_ct == 4:
@@ -1459,7 +1460,7 @@ def build_mesh_faces_of_volume_elements(
else:
FreeCAD.Console.PrintError(
"Error in build_mesh_faces_of_volume_elements(): "
"hexa20: face not found! {}\n"
"tetra4: face not found! {}\n"
.format(face_node_indexs)
)
elif vol_node_ct == 20:
@@ -1503,7 +1504,7 @@ def build_mesh_faces_of_volume_elements(
else:
FreeCAD.Console.PrintError(
"Error in build_mesh_faces_of_volume_elements(): "
"hexa20: face not found! {}\n"
"hexa8: face not found! {}\n"
.format(face_node_indexs)
)
elif vol_node_ct == 15:
@@ -1543,7 +1544,7 @@ def build_mesh_faces_of_volume_elements(
else:
FreeCAD.Console.PrintError(
"Error in build_mesh_faces_of_volume_elements(): "
"pent6: face not found! {}\n"
"penta6: face not found! {}\n"
.format(face_node_indexs)
)
else:
@@ -1699,17 +1700,17 @@ def get_contact_obj_faces(
"(example: multiple element faces per master or slave\n"
)
FreeCAD.Console.PrintLog("Slave: {}, {}\n".format(slave_ref[0].Name, slave_ref))
FreeCAD.Console.PrintLog("Master: {}, {}\n".format(master_ref[0].Name, master_ref))
FreeCAD.Console.PrintLog(" Slave: {}, {}\n".format(slave_ref[0].Name, slave_ref))
FreeCAD.Console.PrintLog(" Master: {}, {}\n".format(master_ref[0].Name, master_ref))
if is_solid_femmesh(femmesh):
# get the nodes, sorted and duplicates removed
FreeCAD.Console.PrintLog(" Get the nodes, sorted and duplicates removed.\n")
slaveface_nds = sorted(list(set(get_femnodes_by_refshape(femmesh, slave_ref))))
masterface_nds = sorted(list(set(get_femnodes_by_refshape(femmesh, master_ref))))
# FreeCAD.Console.PrintLog("slaveface_nds: {}\n".format(slaveface_nds))
# FreeCAD.Console.PrintLog("masterface_nds: {}\n".format(slaveface_nds))
FreeCAD.Console.PrintLog(" slaveface_nds: {}\n".format(slaveface_nds))
FreeCAD.Console.PrintLog(" masterface_nds: {}\n".format(slaveface_nds))
# fill the bit_pattern_dict and search for the faces
FreeCAD.Console.PrintLog(" Fill the bit_pattern_dict and search for the faces.\n")
slave_bit_pattern_dict = get_bit_pattern_dict(
femelement_table,
femnodes_ele_table,
@@ -1721,16 +1722,18 @@ def get_contact_obj_faces(
masterface_nds
)
# get the faces ids
FreeCAD.Console.PrintLog(" Get the FaceIDs.\n")
slave_faces = get_ccxelement_faces_from_binary_search(slave_bit_pattern_dict)
master_faces = get_ccxelement_faces_from_binary_search(master_bit_pattern_dict)
elif is_face_femmesh(femmesh):
slave_ref_shape = slave_ref[0].Shape.getElement(slave_ref[1][0])
master_ref_shape = master_ref[0].Shape.getElement(master_ref[1][0])
# get the faces ids
FreeCAD.Console.PrintLog(" Get the FaceIDs.\n")
slave_face_ids = femmesh.getFacesByFace(slave_ref_shape)
master_face_ids = femmesh.getFacesByFace(master_ref_shape)
# build slave_faces and master_faces
# face 2 for tria6 element
# is it face 2 for all shell elements
@@ -1739,8 +1742,13 @@ def get_contact_obj_faces(
for fid in master_face_ids:
master_faces.append([fid, 2])
FreeCAD.Console.PrintLog("slave_faces: {}\n".format(slave_faces))
FreeCAD.Console.PrintLog("master_faces: {}\n".format(master_faces))
FreeCAD.Console.PrintLog(" Master and slave face ready to use for writer:\n")
FreeCAD.Console.PrintLog(" slave_faces: {}\n".format(slave_faces))
FreeCAD.Console.PrintLog(" master_faces: {}\n".format(master_faces))
if len(slave_faces) == 0:
FreeCAD.Console.PrintError("No faces found for contact slave face.\n")
if len(master_faces) == 0:
FreeCAD.Console.PrintError("No faces found for contact master face.\n")
return [slave_faces, master_faces]
@@ -1846,21 +1854,36 @@ def get_analysis_group_elements(
aAnalysis,
aPart
):
""" all Reference shapes of all Analysis member are searched in the Shape of aPart.
If found in shape they are added to a dict
{ConstraintName : ["ShapeType of the Elements"], [ElementID, ElementID, ...], ...}
"""
all Reference shapes of all Analysis member are searched in the Shape of aPart.
If found in shape they are added to a dict
{ConstraintName : ["ShapeType of the Elements"], [ElementID, ElementID, ...], ...}
"""
from femtools.femutils import is_of_type
group_elements = {} # { name : [element, element, ... , element]}
empty_references = []
# find the objects with empty references, if there are more than one of this type
# they are for all shapes not in the references of the other objects
# ATM: empty references if there are more than one obj of this type are allowed for:
# solid meshes: material
# face meshes: materials, ShellThickness
# edge meshes: material, BeamSection/FluidSection
# BTW: some constraints do have empty references in any case (ex. constraint self weight)
for m in aAnalysis.Group:
if hasattr(m, "References") and "ReadOnly" not in m.getEditorMode("References"):
# some C++ Constraints have a not used References Property
# it is set to Hidden in ReadOnly and PropertyEditor
if m.References:
if hasattr(m, "References"):
if len(m.References) > 0:
grp_ele = get_reference_group_elements(m, aPart)
group_elements[grp_ele[0]] = grp_ele[1]
else:
FreeCAD.Console.PrintMessage(" Empty reference: " + m.Name + "\n")
elif (
len(m.References) == 0
and (
is_of_type(m, "Fem::Material")
# TODO test and implement ElementGeometry1D and ElementGeometry2D
# or is_of_type(m, "Fem::ElementGeometry1D")
# or is_of_type(m, "Fem::ElementGeometry2D")
)
):
FreeCAD.Console.PrintMessage(" Empty reference: {}\n".format(m.Name))
empty_references.append(m)
if empty_references:
if len(empty_references) == 1:
@@ -1876,11 +1899,8 @@ def get_analysis_group_elements(
FreeCAD.Console.PrintMessage(
"We are going to try to get the empty material references anyway.\n"
)
# FemElementGeometry2D, ElementGeometry1D and
# FemElementFluid1D could have empty references,
# but on solid meshes only materials should have empty references
for er in empty_references:
FreeCAD.Console.PrintMessage(er.Name + "\n")
FreeCAD.Console.PrintMessage("{}\n".format(er.Name))
group_elements = get_anlysis_empty_references_group_elements(
group_elements,
aAnalysis,
@@ -1990,12 +2010,9 @@ def get_anlysis_empty_references_group_elements(
aAnalysis,
aShape
):
"""get the elementIDs if the Reference shape is empty
"""
get the elementIDs if the Reference shape is empty
see get_analysis_group_elements() for more information
on solid meshes only material objects could have an
empty reference without there being something wrong!
face meshes could have empty ShellThickness and
edge meshes could have empty BeamSection/FluidSection
"""
# FreeCAD.Console.PrintMessage("{}\n".format(group_elements))
material_ref_shapes = []
@@ -66,3 +66,9 @@ class _FemConstraintSelfWeight(FemConstraint.Proxy):
obj.Gravity_x = 0.0
obj.Gravity_y = 0.0
obj.Gravity_z = -1.0
# https://wiki.freecadweb.org/Scripted_objects#Property_Type
# https://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709
# https://forum.freecadweb.org/viewtopic.php?t=25524
# obj.setEditorMode("References", 1) # read only in PropertyEditor, but writeable by Python
obj.setEditorMode("References", 2) # do not show in Editor
+4
View File
@@ -94,6 +94,10 @@ SET(Core_SRCS
Core/Utilities.h
Core/Visitor.cpp
Core/Visitor.h
Core/CylinderFit.cpp
Core/CylinderFit.h
Core/SphereFit.cpp
Core/SphereFit.h
)
SOURCE_GROUP("Core" FILES ${Core_SRCS})
+93 -4
View File
@@ -32,6 +32,8 @@
#include "Approximation.h"
#include "Elements.h"
#include "Utilities.h"
#include "CylinderFit.h"
#include "SphereFit.h"
#include <Base/BoundBox.h>
#include <Base/Console.h>
@@ -1051,6 +1053,29 @@ float CylinderFit::Fit()
_fRadius = float(radius);
_fLastResult = double(fit);
#if defined(_DEBUG)
Base::Console().Message(" WildMagic Cylinder Fit: Base: (%0.4f, %0.4f, %0.4f), Axis: (%0.6f, %0.6f, %0.6f), Radius: %0.4f, Std Dev: %0.4f\n",
_vBase.x, _vBase.y, _vBase.z, _vAxis.x, _vAxis.y, _vAxis.z, _fRadius, GetStdDeviation());
#endif
MeshCoreFit::CylinderFit cylFit;
cylFit.AddPoints(_vPoints);
//cylFit.SetApproximations(_fRadius, Base::Vector3d(_vBase.x, _vBase.y, _vBase.z), Base::Vector3d(_vAxis.x, _vAxis.y, _vAxis.z));
float result = cylFit.Fit();
if (result < FLOAT_MAX) {
Base::Vector3d base = cylFit.GetBase();
Base::Vector3d dir = cylFit.GetAxis();
#if defined(_DEBUG)
Base::Console().Message("MeshCoreFit::Cylinder Fit: Base: (%0.4f, %0.4f, %0.4f), Axis: (%0.6f, %0.6f, %0.6f), Radius: %0.4f, Std Dev: %0.4f, Iterations: %d\n",
base.x, base.y, base.z, dir.x, dir.y, dir.z, cylFit.GetRadius(), cylFit.GetStdDeviation(), cylFit.GetNumIterations());
#endif
_vBase = Base::convertTo<Base::Vector3f>(base);
_vAxis = Base::convertTo<Base::Vector3f>(dir);
_fRadius = (float)cylFit.GetRadius();
_fLastResult = result;
}
#else
int m = static_cast<int>(_vPoints.size());
int n = 7;
@@ -1238,24 +1263,88 @@ float SphereFit::Fit()
_fRadius = float(sphere.Radius);
// TODO
_fLastResult = 0;
#if defined(_DEBUG)
Base::Console().Message(" WildMagic Sphere Fit: Center: (%0.4f, %0.4f, %0.4f), Radius: %0.4f, Std Dev: %0.4f\n",
_vCenter.x, _vCenter.y, _vCenter.z, _fRadius, GetStdDeviation());
#endif
MeshCoreFit::SphereFit sphereFit;
sphereFit.AddPoints(_vPoints);
sphereFit.ComputeApproximations();
float result = sphereFit.Fit();
if (result < FLOAT_MAX) {
Base::Vector3d center = sphereFit.GetCenter();
#if defined(_DEBUG)
Base::Console().Message("MeshCoreFit::Sphere Fit: Center: (%0.4f, %0.4f, %0.4f), Radius: %0.4f, Std Dev: %0.4f, Iterations: %d\n",
center.x, center.y, center.z, sphereFit.GetRadius(), sphereFit.GetStdDeviation(), sphereFit.GetNumIterations());
#endif
_vCenter = Base::convertTo<Base::Vector3f>(center);
_fRadius = (float)sphereFit.GetRadius();
_fLastResult = result;
}
return _fLastResult;
}
float SphereFit::GetDistanceToSphere(const Base::Vector3f &) const
float SphereFit::GetDistanceToSphere(const Base::Vector3f& rcPoint) const
{
return FLOAT_MAX;
float fResult = FLOAT_MAX;
if (_bIsFitted) {
fResult = Base::Vector3f(rcPoint - _vCenter).Length() - _fRadius;
}
return fResult;
}
float SphereFit::GetStdDeviation() const
{
return FLOAT_MAX;
// Mean: M=(1/N)*SUM Xi
// Variance: VAR=(N/N-3)*[(1/N)*SUM(Xi^2)-M^2]
// Standard deviation: SD=SQRT(VAR)
// Standard error of the mean: SE=SD/SQRT(N)
if (!_bIsFitted)
return FLOAT_MAX;
float fSumXi = 0.0f, fSumXi2 = 0.0f,
fMean = 0.0f, fDist = 0.0f;
float ulPtCt = float(CountPoints());
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt) {
fDist = GetDistanceToSphere( *cIt );
fSumXi += fDist;
fSumXi2 += ( fDist * fDist );
}
fMean = (1.0f / ulPtCt) * fSumXi;
return sqrt((ulPtCt / (ulPtCt - 3.0f)) * ((1.0f / ulPtCt) * fSumXi2 - fMean * fMean));
}
void SphereFit::ProjectToSphere()
{
for (std::list< Base::Vector3f >::iterator it = _vPoints.begin(); it != _vPoints.end(); ++it) {
Base::Vector3f& cPnt = *it;
// Compute unit vector from sphere centre to point.
// Because this vector is orthogonal to the sphere's surface at the
// intersection point we can easily compute the projection point on the
// closest surface point using the radius of the sphere
Base::Vector3f diff = cPnt - _vCenter;
double length = diff.Length();
if (length == 0.0)
{
// Point is exactly at the sphere center, so it can be projected in any direction onto the sphere!
// So here just project in +Z direction
cPnt.z += _fRadius;
}
else
{
diff /= length; // normalizing the vector
cPnt = _vCenter + diff * _fRadius;
}
}
}
// -------------------------------------------------------------------------------
+654
View File
@@ -0,0 +1,654 @@
/***************************************************************************
* Copyright (c) 2020 Graeme van der Vlugt *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
// Definitions:
// Cylinder axis goes through a point (Xc,Yc,Zc) and has direction (L,M,N)
// Cylinder radius is R
// A point on the axis (X0i,Y0i,Z0i) can be described by:
// (X0i,Y0i,Z0i) = (Xc,Yc,Zc) + s(L,M,N)
// where s is the distance from (Xc,Yc,Zc) to (X0i,Y0i,Z0i) when (L,M,N) is
// of unit length (normalized).
// The distance between a cylinder surface point (Xi,Yi,Zi) and its
// projection onto the axis (X0i,Y0i,Z0i) is the radius:
// (Xi - X0i)^2 + (Yi - Y0i)^2 + (Zi - Z0i)^2 = R^2
// Also the vector to a cylinder surface point (Xi,Yi,Zi) from its
// projection onto the axis (X0i,Y0i,Z0i) is orthogonal to the axis so we can
// write:
// (Xi - X0i, Yi - Y0i, Zi - Z0i).(L,M,N) = 0 or
// L(Xi - X0i) + M(Yi - Y0i) + N(Zi - Z0i) = 0
// If we substitute these various equations into each other and further add
// the constraint that L^2 + M^2 + N^2 = 1 then we can arrive at a single
// equation for the cylinder surface points:
// (Xi - Xc + L*L*(Xc - Xi) + L*M*(Yc - Yi) + L*N*(Zc - Zi))^2 +
// (Yi - Yc + M*L*(Xc - Xi) + M*M*(Yc - Yi) + M*N*(Zc - Zi))^2 +
// (Zi - Zc + N*L*(Xc - Xi) + N*M*(Yc - Yi) + N*N*(Zc - Zi))^2 - R^2 = 0
// This equation is what is used in the least squares solution below. Because
// we are constraining the direction vector to a unit length and also because
// we need to stop the axis point from moving along the axis we need to fix one
// of the ordinates in the solution. So from our initial approximations for the
// axis direction (L0,M0,N0):
// if (L0 > M0) && (L0 > N0) then fix Xc = 0 and use L = sqrt(1 - M^2 - N^2)
// else if (M0 > L0) && (M0 > N0) then fix Yc = 0 and use M = sqrt(1 - L^2 - N^2)
// else if (N0 > L0) && (N0 > M0) then fix Zc = 0 and use N = sqrt(1 - L^2 - M^2)
// We thus solve for 5 unknown parameters.
// Thus for the solution to succeed the initial axis direction should be reasonable.
#include "PreCompiled.h"
#ifndef _PreComp_
# include <algorithm>
# include <cstdlib>
# include <iterator>
#endif
#include "CylinderFit.h"
#include <Base/Console.h>
#include <Mod/Mesh/App/WildMagic4/Wm4ApprLineFit3.h>
using namespace MeshCoreFit;
CylinderFit::CylinderFit()
: _vBase(0,0,0)
, _vAxis(0,0,1)
, _dRadius(0)
, _numIter(0)
, _posConvLimit(0.0001)
, _dirConvLimit(0.000001)
, _vConvLimit(0.001)
, _maxIter(50)
{
}
CylinderFit::~CylinderFit()
{
}
// Set approximations before calling the fitting
void CylinderFit::SetApproximations(double radius, const Base::Vector3d &base, const Base::Vector3d &axis)
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
_dRadius = radius;
_vBase = base;
_vAxis = axis;
_vAxis.Normalize();
}
// Set iteration convergence criteria for the fit if special values are needed.
// The default values set in the constructor are suitable for most uses
void CylinderFit::SetConvergenceCriteria(double posConvLimit, double dirConvLimit, double vConvLimit, int maxIter)
{
if (posConvLimit > 0.0)
_posConvLimit = posConvLimit;
if (dirConvLimit > 0.0)
_dirConvLimit = dirConvLimit;
if (vConvLimit > 0.0)
_vConvLimit = vConvLimit;
if (maxIter > 0)
_maxIter = maxIter;
}
double CylinderFit::GetRadius() const
{
if (_bIsFitted)
return _dRadius;
else
return 0.0;
}
Base::Vector3d CylinderFit::GetBase() const
{
if (_bIsFitted)
return _vBase;
else
return Base::Vector3d();
}
Base::Vector3d CylinderFit::GetAxis() const
{
if (_bIsFitted)
return _vAxis;
else
return Base::Vector3d();
}
int CylinderFit::GetNumIterations() const
{
if (_bIsFitted)
return _numIter;
else
return 0;
}
float CylinderFit::GetDistanceToCylinder(const Base::Vector3f &rcPoint) const
{
float fResult = FLOAT_MAX;
if (_bIsFitted)
{
fResult = Base::Vector3d(rcPoint.x, rcPoint.y, rcPoint.z).DistanceToLine(_vBase, _vAxis) - _dRadius;
}
return fResult;
}
float CylinderFit::GetStdDeviation() const
{
// Mean: M=(1/N)*SUM Xi
// Variance: VAR=(N/N-3)*[(1/N)*SUM(Xi^2)-M^2]
// Standard deviation: SD=SQRT(VAR)
// Standard error of the mean: SE=SD/SQRT(N)
if (!_bIsFitted)
return FLOAT_MAX;
float fSumXi = 0.0f, fSumXi2 = 0.0f,
fMean = 0.0f, fDist = 0.0f;
float ulPtCt = float(CountPoints());
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt) {
fDist = GetDistanceToCylinder( *cIt );
fSumXi += fDist;
fSumXi2 += ( fDist * fDist );
}
fMean = (1.0f / ulPtCt) * fSumXi;
return sqrt((ulPtCt / (ulPtCt - 3.0f)) * ((1.0f / ulPtCt) * fSumXi2 - fMean * fMean));
}
void CylinderFit::ProjectToCylinder()
{
Base::Vector3f cBase(_vBase.x, _vBase.y, _vBase.z);
Base::Vector3f cAxis(_vAxis.x, _vAxis.y, _vAxis.z);
for (std::list< Base::Vector3f >::iterator it = _vPoints.begin(); it != _vPoints.end(); ++it) {
Base::Vector3f& cPnt = *it;
if (cPnt.DistanceToLine(cBase, cAxis) > 0) {
Base::Vector3f proj;
cBase.ProjectToPlane(cPnt, cAxis, proj);
Base::Vector3f diff = cPnt - proj;
diff.Normalize();
cPnt = proj + diff * _dRadius;
}
else {
// Point is on the cylinder axis, so it can be moved in
// any direction perpendicular to the cylinder axis
Base::Vector3f cMov(cPnt);
do {
float x = (float(rand()) / float(RAND_MAX));
float y = (float(rand()) / float(RAND_MAX));
float z = (float(rand()) / float(RAND_MAX));
cMov.Move(x,y,z);
}
while (cMov.DistanceToLine(cBase, cAxis) == 0);
Base::Vector3f proj;
cMov.ProjectToPlane(cPnt, cAxis, proj);
Base::Vector3f diff = cPnt - proj;
diff.Normalize();
cPnt = proj + diff * _dRadius;
}
}
}
// Compute approximations for the parameters using all points by computing a
// line through the points. This doesn't work well if the points are only from
// one small surface area.
// In that case rather use SetApproximations() with a better estimate.
void CylinderFit::ComputeApproximationsLine()
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
_vBase.Set(0.0, 0.0, 0.0);
_vAxis.Set(0.0, 0.0, 0.0);
_dRadius = 0.0;
if (_vPoints.size() > 0)
{
std::vector<Wm4::Vector3d> input;
std::transform(_vPoints.begin(), _vPoints.end(), std::back_inserter(input),
[](const Base::Vector3f& v) { return Wm4::Vector3d(v.x, v.y, v.z); });
Wm4::Line3<double> kLine = Wm4::OrthogonalLineFit3(input.size(), input.data());
_vBase.Set(kLine.Origin.X(), kLine.Origin.Y(), kLine.Origin.Z());
_vAxis.Set(kLine.Direction.X(), kLine.Direction.Y(), kLine.Direction.Z());
for (std::list< Base::Vector3f >::const_iterator cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt)
_dRadius += Base::Vector3d(cIt->x, cIt->y, cIt->z).DistanceToLine(_vBase, _vAxis);
_dRadius /= (double)_vPoints.size();
}
}
float CylinderFit::Fit()
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
// A minimum of 5 surface points is needed to define a cylinder
if (CountPoints() < 5)
return FLOAT_MAX;
// If approximations have not been set/computed then compute some now using the line fit method
if (_dRadius == 0.0)
ComputeApproximationsLine();
// Check parameters to define the best solution direction
// There are 7 parameters but 2 are actually dependent on the others
// so we are actually solving for 5 parameters.
// order of parameters depending on the solution direction:
// solL: Yc, Zc, M, N, R
// solM: Xc, Zc, L, N, R
// solN: Xc, Yc, L, M, R
SolutionD solDir;
findBestSolDirection(solDir);
// Initialise some matrices and vectors
std::vector< Base::Vector3d > residuals(CountPoints(), Base::Vector3d(0.0, 0.0, 0.0));
Matrix5x5 atpa;
Eigen::VectorXd atpl(5);
// Iteration loop...
double sigma0;
bool cont = true;
while (cont && (_numIter < _maxIter))
{
++_numIter;
// Set up the quasi parameteric normal equations
setupNormalEquationMatrices(solDir, residuals, atpa, atpl);
// Solve the equations for the unknown corrections
Eigen::LLT< Matrix5x5 > llt(atpa);
if (llt.info() != Eigen::Success)
return FLOAT_MAX;
Eigen::VectorXd x = llt.solve(atpl);
// Check parameter convergence
cont = false;
if ((fabs(x(0)) > _posConvLimit) || (fabs(x(1)) > _posConvLimit) || // the two position parameter corrections
(fabs(x(2)) > _dirConvLimit) || (fabs(x(3)) > _dirConvLimit) || // the two direction parameter corrections
(fabs(x(4)) > _posConvLimit)) // the radius correction
cont = true;
// Before updating the unknowns, compute the residuals and sigma0 and check the residual convergence
bool vConverged;
if (!computeResiduals(solDir, x, residuals, sigma0, _vConvLimit, vConverged))
return FLOAT_MAX;
if (!vConverged)
cont = true;
// Update the parameters
if (!updateParameters(solDir, x))
return FLOAT_MAX;
}
// Check for convergence
if (cont)
return FLOAT_MAX;
_bIsFitted = true;
_fLastResult = sigma0;
return _fLastResult;
}
// Checks initial parameter values and defines the best solution direction to use
// Axis direction = (L,M,N)
// solution L: L is biggest axis component and L = f(M,N) and X = 0 (we move the base point along axis so that x = 0)
// solution M: M is biggest axis component and M = f(L,N) and Y = 0 (we move the base point along axis so that y = 0)
// solution N: N is biggest axis component and N = f(L,M) and Z = 0 (we move the base point along axis so that z = 0)
// IMPLEMENT: use fix X,Y,or Z to value of associated centre of gravity coordinate
// (because 0 could be along way away from cylinder points)
void CylinderFit::findBestSolDirection(SolutionD &solDir)
{
// Choose the best of the three solution 'directions' to use
// This is to avoid a square root of a negative number when computing the dependent parameters
Base::Vector3d dir = _vAxis;
Base::Vector3d pos = _vBase;
dir.Normalize();
double biggest = dir.x;
solDir = solL;
if (fabs (dir.y) > fabs (biggest))
{
biggest = dir.y;
solDir = solM;
}
if (fabs (dir.z) > fabs (biggest))
{
biggest = dir.z;
solDir = solN;
}
if (biggest < 0.0)
dir.Set(-dir.x, -dir.y, -dir.z); // multiplies by -1
double lambda;
switch (solDir)
{
case solL:
lambda = -pos.x / dir.x;
pos.x = 0.0;
pos.y = pos.y + lambda * dir.y;
pos.z = pos.z + lambda * dir.z;
break;
case solM:
lambda = -pos.y / dir.y;
pos.x = pos.x + lambda * dir.x;
pos.y = 0.0;
pos.z = pos.z + lambda * dir.z;
break;
case solN:
lambda = -pos.z / dir.z;
pos.x = pos.x + lambda * dir.x;
pos.y = pos.y + lambda * dir.y;
pos.z = 0.0;
break;
}
_vAxis = dir;
_vBase = pos;
}
// Set up the normal equation matrices
// atpa ... 5x5 normal matrix
// atpl ... 5x1 matrix (right-hand side of equation)
void CylinderFit::setupNormalEquationMatrices(SolutionD solDir, const std::vector< Base::Vector3d > &residuals, Matrix5x5 &atpa, Eigen::VectorXd &atpl) const
{
// Zero matrices
atpa.setZero();
atpl.setZero();
// For each point, setup the observation equation coefficients and add their
// contribution into the the normal equation matrices
double a[5], b[3];
double f0, qw;
std::vector< Base::Vector3d >::const_iterator vIt = residuals.begin();
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt, ++vIt)
{
// if (using this point) { // currently all given points are used (could modify this if eliminating outliers, etc....
setupObservation(solDir, *cIt, *vIt, a, f0, qw, b);
addObservationU(a, f0, qw, atpa, atpl);
// }
}
setLowerPart(atpa);
}
// Sets up contributions of given observation to the quasi parameteric
// normal equation matrices. Assumes uncorrelated coordinates.
// point ... point
// residual ... residual for this point computed from previous iteration (zero for first iteration)
// a[5] ... parameter partials
// f0 ... reference to f0 term
// qw ... reference to quasi weight (here we are assuming equal unit weights for each observed point coordinate)
// b[3] ... observation partials
void CylinderFit::setupObservation(SolutionD solDir, const Base::Vector3f &point, const Base::Vector3d &residual, double a[5], double &f0, double &qw, double b[3]) const
{
// This adjustment requires an update of the observation approximations
// because the residuals do not have a linear relationship.
// New estimates for the observations:
double xEstimate = (double)point.x + residual.x;
double yEstimate = (double)point.y + residual.y;
double zEstimate = (double)point.z + residual.z;
// intermediate parameters
double lambda = _vAxis.x * (xEstimate - _vBase.x) + _vAxis.y * (yEstimate - _vBase.y) + _vAxis.z * (zEstimate - _vBase.z);
double x0 = _vBase.x + lambda * _vAxis.x;
double y0 = _vBase.y + lambda * _vAxis.y;
double z0 = _vBase.z + lambda * _vAxis.z;
double dx = xEstimate - x0;
double dy = yEstimate - y0;
double dz = zEstimate - z0;
double dx00 = _vBase.x - xEstimate;
double dy00 = _vBase.y - yEstimate;
double dz00 = _vBase.z - zEstimate;
// partials of the observations
b[0] = 2.0 * (dx - _vAxis.x * _vAxis.x * dx - _vAxis.x * _vAxis.y * dy - _vAxis.x * _vAxis.z * dz);
b[1] = 2.0 * (dy - _vAxis.x * _vAxis.y * dx - _vAxis.y * _vAxis.y * dy - _vAxis.y * _vAxis.z * dz);
b[2] = 2.0 * (dz - _vAxis.x * _vAxis.z * dx - _vAxis.y * _vAxis.z * dy - _vAxis.z * _vAxis.z * dz);
// partials of the parameters
switch (solDir)
{
double ddxdl, ddydl, ddzdl;
double ddxdm, ddydm, ddzdm;
double ddxdn, ddydn, ddzdn;
case solL:
// order of parameters: Yc, Zc, M, N, R
ddxdm = -2.0 * _vAxis.y * dx00 + (_vAxis.x - _vAxis.y * _vAxis.y / _vAxis.x) * dy00 - (_vAxis.y * _vAxis.z / _vAxis.x) * dz00;
ddydm = (_vAxis.x - _vAxis.y * _vAxis.y / _vAxis.x) * dx00 + 2.0 * _vAxis.y * dy00 + _vAxis.z * dz00;
ddzdm = -(_vAxis.y * _vAxis.z / _vAxis.x) * dx00 + _vAxis.z * dy00;
ddxdn = -2.0 * _vAxis.z * dx00 - (_vAxis.y * _vAxis.z / _vAxis.x) * dy00 + (_vAxis.x - _vAxis.z * _vAxis.z / _vAxis.x) * dz00;
ddydn = -(_vAxis.y * _vAxis.z / _vAxis.x) * dx00 + _vAxis.y * dz00;
ddzdn = (_vAxis.x - _vAxis.z * _vAxis.z / _vAxis.x) * dx00 + _vAxis.y * dy00 + 2.0 * _vAxis.z * dz00;
a[0] = -b[1];
a[1] = -b[2];
a[2] = 2.0 * (dx * ddxdm + dy * ddydm + dz * ddzdm);
a[3] = 2.0 * (dx * ddxdn + dy * ddydn + dz * ddzdn);
a[4] = -2.0 * _dRadius;
break;
case solM:
// order of parameters: Xc, Zc, L, N, R
ddxdl = 2.0 * _vAxis.x * dx00 + (_vAxis.y - _vAxis.x * _vAxis.x / _vAxis.y) * dy00 + _vAxis.z * dz00;
ddydl = (_vAxis.y - _vAxis.x * _vAxis.x / _vAxis.y) * dx00 - 2.0 * _vAxis.x * dy00 - (_vAxis.x * _vAxis.z / _vAxis.y) * dz00;
ddzdl = _vAxis.z * dx00 - (_vAxis.x * _vAxis.z / _vAxis.y) * dy00;
ddxdn = -(_vAxis.x * _vAxis.z / _vAxis.y) * dy00 + _vAxis.x * dz00;
ddydn = -(_vAxis.x * _vAxis.z / _vAxis.y) * dx00 - 2.0 * _vAxis.z * dy00 + (_vAxis.y - _vAxis.z * _vAxis.z / _vAxis.y) * dz00;
ddzdn = _vAxis.x * dx00 + (_vAxis.y - _vAxis.z * _vAxis.z / _vAxis.y) * dy00 + 2.0 * _vAxis.z * dz00;
a[0] = -b[0];
a[1] = -b[2];
a[2] = 2.0 * (dx * ddxdl + dy * ddydl + dz * ddzdl);
a[3] = 2.0 * (dx * ddxdn + dy * ddydn + dz * ddzdn);
a[4] = -2.0 * _dRadius;
break;
case solN:
// order of parameters: Xc, Yc, L, M, R
ddxdl = 2.0 * _vAxis.x * dx00 + _vAxis.y * dy00 + (_vAxis.z - _vAxis.x * _vAxis.x / _vAxis.z) * dz00;
ddydl = _vAxis.y * dx00 - (_vAxis.x * _vAxis.y / _vAxis.z) * dz00;
ddzdl = (_vAxis.z - _vAxis.x * _vAxis.x / _vAxis.z) * dx00 - (_vAxis.x * _vAxis.y / _vAxis.z) * dy00 - 2.0 * _vAxis.x * dz00;
ddxdm = _vAxis.x * dy00 - (_vAxis.x * _vAxis.y / _vAxis.z) * dz00;
ddydm = _vAxis.x * dx00 + 2.0 * _vAxis.y * dy00 + (_vAxis.z - _vAxis.y * _vAxis.y / _vAxis.z) * dz00;
ddzdm = - (_vAxis.x * _vAxis.y / _vAxis.z) * dx00 + (_vAxis.z - _vAxis.y * _vAxis.y / _vAxis.z) * dy00 - 2.0 * _vAxis.y * dz00;
a[0] = -b[0];
a[1] = -b[1];
a[2] = 2.0 * (dx * ddxdl + dy * ddydl + dz * ddzdl);
a[3] = 2.0 * (dx * ddxdm + dy * ddydm + dz * ddzdm);
a[4] = -2.0 * _dRadius;
break;
}
// free term
f0 = _dRadius * _dRadius - dx * dx - dy * dy - dz * dz + b[0] * residual.x + b[1] * residual.y + b[2] * residual.z;
// quasi weight (using equal weights for cylinder point coordinate observations)
//w[0] = 1.0;
//w[1] = 1.0;
//w[2] = 1.0;
//qw = 1.0 / (b[0] * b[0] / w[0] + b[1] * b[1] / w[1] + b[2] * b[2] / w[2]);
qw = 1.0 / (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
}
// Computes contribution of the given observation equation on the normal equation matrices
// Call this for each observation (point)
// Here we only add the contribution to the upper part of the normal matrix
// and then after all observations have been added we need to set the lower part
// (which is symmetrical to the upper part)
// a[5] ... parameter partials
// li ... free term (f0)
// pi ... weight of observation (= quasi weight qw for this solution)
// atpa ... 5x5 normal equation matrix
// atpl ... 5x1 matrix/vector (right-hand side of equations)
void CylinderFit::addObservationU(double a[5], double li, double pi, Matrix5x5 &atpa, Eigen::VectorXd &atpl) const
{
for (int i = 0; i < 5; ++i)
{
double aipi = a[i] * pi;
for (int j = i; j < 5; ++j)
{
atpa(i, j) += aipi * a[j];
//atpa(j, i) = atpa(i, j); // it's a symmetrical matrix, we'll set this later after all observations processed
}
atpl(i) += aipi * li;
}
}
// Set the lower part of the normal matrix equal to the upper part
// This is done after all the observations have been added
void CylinderFit::setLowerPart(Matrix5x5 &atpa) const
{
for (int i = 0; i < 5; ++i)
for (int j = i+1; j < 5; ++j) // skip the diagonal elements
atpa(j, i) = atpa(i, j);
}
// Compute the residuals and sigma0 and check the residual convergence
bool CylinderFit::computeResiduals(SolutionD solDir, const Eigen::VectorXd &x, std::vector< Base::Vector3d > &residuals, double &sigma0, double vConvLimit, bool &vConverged) const
{
vConverged = true;
int nPtsUsed = 0;
sigma0 = 0.0;
double a[5], b[3];
double f0, qw;
//double maxdVx = 0.0;
//double maxdVy = 0.0;
//double maxdVz = 0.0;
//double rmsVv = 0.0;
std::vector< Base::Vector3d >::iterator vIt = residuals.begin();
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt, ++vIt)
{
// if (using this point) { // currently all given points are used (could modify this if eliminating outliers, etc....
++nPtsUsed;
Base::Vector3d &v = *vIt;
setupObservation(solDir, *cIt, v, a, f0, qw, b);
double qv = -f0;
for (int i = 0; i < 5; ++i)
qv += a[i] * x(i);
// We are using equal weights for cylinder point coordinate observations (see setupObservation)
// i.e. w[0] = w[1] = w[2] = 1.0;
//double vx = -qw * qv * b[0] / w[0];
//double vy = -qw * qv * b[1] / w[1];
//double vz = -qw * qv * b[2] / w[2];
double vx = -qw * qv * b[0];
double vy = -qw * qv * b[1];
double vz = -qw * qv * b[2];
double dVx = fabs(vx - v.x);
double dVy = fabs(vy - v.y);
double dVz = fabs(vz - v.z);
v.x = vx;
v.y = vy;
v.z = vz;
//double vv = v.x * v.x + v.y * v.y + v.z * v.z;
//rmsVv += vv * vv;
//sigma0 += v.x * w[0] * v.x + v.y * w[1] * v.y + v.z * w[2] * v.z;
sigma0 += v.x * v.x + v.y * v.y + v.z * v.z;
if ((dVx > vConvLimit) || (dVy > vConvLimit) || (dVz > vConvLimit))
vConverged = false;
//if (dVx > maxdVx)
// maxdVx = dVx;
//if (dVy > maxdVy)
// maxdVy = dVy;
//if (dVz > maxdVz)
// maxdVz = dVz;
}
// Compute degrees of freedom and sigma0
if (nPtsUsed < 5) // A minimum of 5 surface points is needed to define a cylinder
{
sigma0 = 0.0;
return false;
}
int df = nPtsUsed - 5;
if (df == 0)
sigma0 = 0.0;
else
sigma0 = sqrt (sigma0 / (double)df);
//rmsVv = sqrt(rmsVv / (double)nPtsUsed);
//Base::Console().Message("X: %0.3e %0.3e %0.3e %0.3e %0.3e , Max dV: %0.4f %0.4f %0.4f , RMS Vv: %0.4f\n", x(0), x(1), x(2), x(3), x(4), maxdVx, maxdVy, maxdVz, rmsVv);
return true;
}
// Update the parameters after solving the normal equations
bool CylinderFit::updateParameters(SolutionD solDir, const Eigen::VectorXd &x)
{
// Update the parameters used as unknowns in the solution
switch (solDir)
{
case solL: // order of parameters: Yc, Zc, M, N, R
_vBase.y += x(0);
_vBase.z += x(1);
_vAxis.y += x(2);
_vAxis.z += x(3);
_dRadius += x(4);
break;
case solM: // order of parameters: Xc, Zc, L, N, R
_vBase.x += x(0);
_vBase.z += x(1);
_vAxis.x += x(2);
_vAxis.z += x(3);
_dRadius += x(4);
break;
case solN: // order of parameters: Xc, Yc, L, M, R
_vBase.x += x(0);
_vBase.y += x(1);
_vAxis.x += x(2);
_vAxis.y += x(3);
_dRadius += x(4);
break;
}
// Update the dependent axis direction parameter
double l2, m2, n2;
switch (solDir)
{
case solL:
l2 = 1.0 - _vAxis.y * _vAxis.y - _vAxis.z * _vAxis.z;
if (l2 <= 0.0)
return false; // L*L <= 0 !
_vAxis.x = sqrt(l2);
//_vBase.x = 0.0; // should already be 0
break;
case solM:
m2 = 1.0 - _vAxis.x * _vAxis.x - _vAxis.z * _vAxis.z;
if (m2 <= 0.0)
return false; // M*M <= 0 !
_vAxis.y = sqrt(m2);
//_vBase.y = 0.0; // should already be 0
break;
case solN:
n2 = 1.0 - _vAxis.x * _vAxis.x - _vAxis.y * _vAxis.y;
if (n2 <= 0.0)
return false; // N*N <= 0 !
_vAxis.z = sqrt(n2);
//_vBase.z = 0.0; // should already be 0
break;
}
return true;
}
+151
View File
@@ -0,0 +1,151 @@
/***************************************************************************
* Copyright (c) 2020 Graeme van der Vlugt *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef MESH_CYLINDER_FIT_H
#define MESH_CYLINDER_FIT_H
#include "Approximation.h"
#include <Eigen/Eigenvalues>
// -------------------------------------------------------------------------------
namespace MeshCoreFit {
typedef Eigen::Matrix<double,5,5,Eigen::RowMajor> Matrix5x5;
/**
* Best-fit cylinder for a given set of points.
* Doesn't expect points on any top or bottom end-planes, only points on the side surface
*/
class MeshExport CylinderFit : public MeshCore::Approximation
{
protected:
// Solution 'direction' enumeration
enum SolutionD {solL = 0, // solution L: L is biggest axis component and L = f(M,N)
solM = 1, // solution M: M is biggest axis component and M = f(L,N)
solN = 2 // solution N: N is biggest axis component and N = f(L,M)
};
public:
/**
* Construction
*/
CylinderFit();
/**
* Destruction
*/
virtual ~CylinderFit();
/**
* Set approximations before calling Fit()
*/
void SetApproximations(double radius, const Base::Vector3d &base, const Base::Vector3d &axis);
/**
* Set iteration convergence criteria for the fit if special values are needed.
* The default values set in the constructor are suitable for most uses
*/
void SetConvergenceCriteria(double posConvLimit, double dirConvLimit, double vConvLimit, int maxIter);
/**
* Returns the radius of the fitted cylinder. If Fit() has not been called then zero is returned.
*/
double GetRadius() const;
/**
* Returns the base of the fitted cylinder. If Fit() has not been called the null vector is returned.
*/
Base::Vector3d GetBase() const;
/**
* Returns the axis of the fitted cylinder. If Fit() has not been called the null vector is returned.
*/
Base::Vector3d GetAxis() const;
/**
* Returns the number of iterations that Fit() needed to converge. If Fit() has not been called then zero is returned.
*/
int GetNumIterations() const;
/**
* Fit a cylinder into the given points. If the fit fails FLOAT_MAX is returned.
*/
float Fit();
/**
* Returns the distance from the point \a rcPoint to the fitted cylinder. If Fit() has not been
* called FLOAT_MAX is returned.
*/
float GetDistanceToCylinder(const Base::Vector3f &rcPoint) const;
/**
* Returns the standard deviation from the points to the fitted cylinder. If Fit() has not been
* called FLOAT_MAX is returned.
*/
float GetStdDeviation() const;
/**
* Projects the points onto the fitted cylinder.
*/
void ProjectToCylinder();
protected:
/**
* Compute approximations for the parameters using all points using the line fit method
*/
void ComputeApproximationsLine();
/**
* Checks initial parameter values and defines the best solution direction to use
*/
void findBestSolDirection(SolutionD &solDir);
/**
* Set up the normal equations
*/
void setupNormalEquationMatrices(SolutionD solDir, const std::vector< Base::Vector3d > &residuals, Matrix5x5 &atpa, Eigen::VectorXd &atpl) const;
/**
* Sets up contributions of given observation to the normal equation matrices.
*/
void setupObservation(SolutionD solDir, const Base::Vector3f &point, const Base::Vector3d &residual, double a[5], double &f0, double &qw, double b[3]) const;
/**
* Computes contribution of the given observation equation on the normal equation matrices
*/
void addObservationU(double a[5], double li, double pi, Matrix5x5 &atpa, Eigen::VectorXd &atpl) const;
/**
* Set the lower part of the normal matrix equal to the upper part
*/
void setLowerPart(Matrix5x5 &atpa) const;
/**
* Compute the residuals and sigma0 and check the residual convergence
*/
bool computeResiduals(SolutionD solDir, const Eigen::VectorXd &x, std::vector< Base::Vector3d > &residuals, double &sigma0, double vConvLimit, bool &vConverged) const;
/**
* Update the parameters after solving the normal equations
*/
bool updateParameters(SolutionD solDir, const Eigen::VectorXd &x);
protected:
Base::Vector3d _vBase; /**< Base vector of the cylinder (point on axis). */
Base::Vector3d _vAxis; /**< Axis of the cylinder. */
double _dRadius; /**< Radius of the cylinder. */
int _numIter; /**< Number of iterations for solution to converge. */
double _posConvLimit; /**< Position and radius parameter convergence threshold. */
double _dirConvLimit; /**< Direction parameter convergence threshold. */
double _vConvLimit; /**< Residual convergence threshold. */
int _maxIter; /**< Maximum number of iterations. */
};
} // namespace MeshCore
#endif // MESH_CYLINDER_FIT_H
+427
View File
@@ -0,0 +1,427 @@
/***************************************************************************
* Copyright (c) 2020 Graeme van der Vlugt *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <algorithm>
# include <cstdlib>
# include <iterator>
#endif
#include "SphereFit.h"
#include <Base/Console.h>
using namespace MeshCoreFit;
SphereFit::SphereFit()
: _vCenter(0,0,0)
, _dRadius(0)
, _numIter(0)
, _posConvLimit(0.0001)
, _vConvLimit(0.001)
, _maxIter(50)
{
}
SphereFit::~SphereFit()
{
}
// Set approximations before calling the fitting
void SphereFit::SetApproximations(double radius, const Base::Vector3d &center)
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
_dRadius = radius;
_vCenter = center;
}
// Set iteration convergence criteria for the fit if special values are needed.
// The default values set in the constructor are suitable for most uses
void SphereFit::SetConvergenceCriteria(double posConvLimit, double vConvLimit, int maxIter)
{
if (posConvLimit > 0.0)
_posConvLimit = posConvLimit;
if (vConvLimit > 0.0)
_vConvLimit = vConvLimit;
if (maxIter > 0)
_maxIter = maxIter;
}
double SphereFit::GetRadius() const
{
if (_bIsFitted)
return _dRadius;
else
return 0.0;
}
Base::Vector3d SphereFit::GetCenter() const
{
if (_bIsFitted)
return _vCenter;
else
return Base::Vector3d();
}
int SphereFit::GetNumIterations() const
{
if (_bIsFitted)
return _numIter;
else
return 0;
}
float SphereFit::GetDistanceToSphere(const Base::Vector3f &rcPoint) const
{
float fResult = FLOAT_MAX;
if (_bIsFitted)
{
fResult = Base::Vector3d((double)rcPoint.x - _vCenter.x, (double)rcPoint.y - _vCenter.y, (double)rcPoint.z - _vCenter.z).Length() - _dRadius;
}
return fResult;
}
float SphereFit::GetStdDeviation() const
{
// Mean: M=(1/N)*SUM Xi
// Variance: VAR=(N/N-3)*[(1/N)*SUM(Xi^2)-M^2]
// Standard deviation: SD=SQRT(VAR)
// Standard error of the mean: SE=SD/SQRT(N)
if (!_bIsFitted)
return FLOAT_MAX;
float fSumXi = 0.0f, fSumXi2 = 0.0f,
fMean = 0.0f, fDist = 0.0f;
float ulPtCt = float(CountPoints());
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt) {
fDist = GetDistanceToSphere( *cIt );
fSumXi += fDist;
fSumXi2 += ( fDist * fDist );
}
fMean = (1.0f / ulPtCt) * fSumXi;
return sqrt((ulPtCt / (ulPtCt - 3.0f)) * ((1.0f / ulPtCt) * fSumXi2 - fMean * fMean));
}
void SphereFit::ProjectToSphere()
{
for (std::list< Base::Vector3f >::iterator it = _vPoints.begin(); it != _vPoints.end(); ++it) {
Base::Vector3f& cPnt = *it;
// Compute unit vector from sphere centre to point.
// Because this vector is orthogonal to the sphere's surface at the
// intersection point we can easily compute the projection point on the
// closest surface point using the radius of the sphere
Base::Vector3d diff((double)cPnt.x - _vCenter.x, (double)cPnt.y - _vCenter.y, (double)cPnt.z - _vCenter.z);
double length = diff.Length();
if (length == 0.0)
{
// Point is exactly at the sphere center, so it can be projected in any direction onto the sphere!
// So here just project in +Z direction
cPnt.z += (float)_dRadius;
}
else
{
diff /= length; // normalizing the vector
Base::Vector3d proj = _vCenter + diff * _dRadius;
cPnt.x = (float)proj.x;
cPnt.y = (float)proj.y;
cPnt.z = (float)proj.z;
}
}
}
// Compute approximations for the parameters using all points:
// Set centre to centre of gravity of points and radius to the average
// distance from the centre of gravity to the points.
void SphereFit::ComputeApproximations()
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
_vCenter.Set(0.0, 0.0, 0.0);
_dRadius = 0.0;
if (_vPoints.size() > 0)
{
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt)
{
_vCenter.x += cIt->x;
_vCenter.y += cIt->y;
_vCenter.z += cIt->z;
}
_vCenter /= (double)_vPoints.size();
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt)
{
Base::Vector3d diff((double)cIt->x - _vCenter.x, (double)cIt->y - _vCenter.y, (double)cIt->z - _vCenter.z);
_dRadius += diff.Length();
}
_dRadius /= (double)_vPoints.size();
}
}
float SphereFit::Fit()
{
_bIsFitted = false;
_fLastResult = FLOAT_MAX;
_numIter = 0;
// A minimum of 4 surface points is needed to define a sphere
if (CountPoints() < 4)
return FLOAT_MAX;
// If approximations have not been set/computed then compute some now
if (_dRadius == 0.0)
ComputeApproximations();
// Initialise some matrices and vectors
std::vector< Base::Vector3d > residuals(CountPoints(), Base::Vector3d(0.0, 0.0, 0.0));
Matrix4x4 atpa;
Eigen::VectorXd atpl(4);
// Iteration loop...
double sigma0;
bool cont = true;
while (cont && (_numIter < _maxIter))
{
++_numIter;
// Set up the quasi parameteric normal equations
setupNormalEquationMatrices(residuals, atpa, atpl);
// Solve the equations for the unknown corrections
Eigen::LLT< Matrix4x4 > llt(atpa);
if (llt.info() != Eigen::Success)
return FLOAT_MAX;
Eigen::VectorXd x = llt.solve(atpl);
// Check parameter convergence (order of parameters: X,Y,Z,R)
cont = false;
if ((fabs(x(0)) > _posConvLimit) || (fabs(x(1)) > _posConvLimit) ||
(fabs(x(2)) > _posConvLimit) || (fabs(x(3)) > _posConvLimit))
cont = true;
// Before updating the unknowns, compute the residuals and sigma0 and check the residual convergence
bool vConverged;
if (!computeResiduals(x, residuals, sigma0, _vConvLimit, vConverged))
return FLOAT_MAX;
if (!vConverged)
cont = true;
// Update the parameters (order of parameters: X,Y,Z,R)
_vCenter.x += x(0);
_vCenter.y += x(1);
_vCenter.z += x(2);
_dRadius += x(3);
}
// Check for convergence
if (cont)
return FLOAT_MAX;
_bIsFitted = true;
_fLastResult = sigma0;
return _fLastResult;
}
// Set up the normal equation matrices
// atpa ... 4x4 normal matrix
// atpl ... 4x1 matrix (right-hand side of equation)
void SphereFit::setupNormalEquationMatrices(const std::vector< Base::Vector3d > &residuals, Matrix4x4 &atpa, Eigen::VectorXd &atpl) const
{
// Zero matrices
atpa.setZero();
atpl.setZero();
// For each point, setup the observation equation coefficients and add their
// contribution into the the normal equation matrices
double a[4], b[3];
double f0, qw;
std::vector< Base::Vector3d >::const_iterator vIt = residuals.begin();
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt, ++vIt)
{
// if (using this point) { // currently all given points are used (could modify this if eliminating outliers, etc....
setupObservation(*cIt, *vIt, a, f0, qw, b);
addObservationU(a, f0, qw, atpa, atpl);
// }
}
setLowerPart(atpa);
}
// Sets up contributions of given observation to the quasi parameteric
// normal equation matrices. Assumes uncorrelated coordinates.
// point ... point
// residual ... residual for this point computed from previous iteration (zero for first iteration)
// a[4] ... parameter partials (order of parameters: X,Y,Z,R)
// f0 ... reference to f0 term
// qw ... reference to quasi weight (here we are assuming equal unit weights for each observed point coordinate)
// b[3] ... observation partials
void SphereFit::setupObservation(const Base::Vector3f &point, const Base::Vector3d &residual, double a[4], double &f0, double &qw, double b[3]) const
{
// This adjustment requires an update of the observation approximations
// because the residuals do not have a linear relationship.
// New estimates for the observations:
double xEstimate = (double)point.x + residual.x;
double yEstimate = (double)point.y + residual.y;
double zEstimate = (double)point.z + residual.z;
// partials of the observations
double dx = xEstimate - _vCenter.x;
double dy = yEstimate - _vCenter.y;
double dz = zEstimate - _vCenter.z;
b[0] = 2.0 * dx;
b[1] = 2.0 * dy;
b[2] = 2.0 * dz;
// partials of the parameters
a[0] = -b[0];
a[1] = -b[1];
a[2] = -b[2];
a[3] = -2.0 * _dRadius;
// free term
f0 = _dRadius * _dRadius - dx * dx - dy * dy - dz * dz + b[0] * residual.x + b[1] * residual.y + b[2] * residual.z;
// quasi weight (using equal weights for sphere point coordinate observations)
//w[0] = 1.0;
//w[1] = 1.0;
//w[2] = 1.0;
//qw = 1.0 / (b[0] * b[0] / w[0] + b[1] * b[1] / w[1] + b[2] * b[2] / w[2]);
qw = 1.0 / (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
}
// Computes contribution of the given observation equation on the normal equation matrices
// Call this for each observation (point)
// Here we only add the contribution to the upper part of the normal matrix
// and then after all observations have been added we need to set the lower part
// (which is symmetrical to the upper part)
// a[4] ... parameter partials
// li ... free term (f0)
// pi ... weight of observation (= quasi weight qw for this solution)
// atpa ... 4x4 normal equation matrix
// atpl ... 4x1 matrix/vector (right-hand side of equations)
void SphereFit::addObservationU(double a[4], double li, double pi, Matrix4x4 &atpa, Eigen::VectorXd &atpl) const
{
for (int i = 0; i < 4; ++i)
{
double aipi = a[i] * pi;
for (int j = i; j < 4; ++j)
{
atpa(i, j) += aipi * a[j];
//atpa(j, i) = atpa(i, j); // it's a symmetrical matrix, we'll set this later after all observations processed
}
atpl(i) += aipi * li;
}
}
// Set the lower part of the normal matrix equal to the upper part
// This is done after all the observations have been added
void SphereFit::setLowerPart(Matrix4x4 &atpa) const
{
for (int i = 0; i < 4; ++i)
for (int j = i+1; j < 4; ++j) // skip the diagonal elements
atpa(j, i) = atpa(i, j);
}
// Compute the residuals and sigma0 and check the residual convergence
bool SphereFit::computeResiduals(const Eigen::VectorXd &x, std::vector< Base::Vector3d > &residuals, double &sigma0, double vConvLimit, bool &vConverged) const
{
vConverged = true;
int nPtsUsed = 0;
sigma0 = 0.0;
double a[4], b[3];
double f0, qw;
//double maxdVx = 0.0;
//double maxdVy = 0.0;
//double maxdVz = 0.0;
//double rmsVv = 0.0;
std::vector< Base::Vector3d >::iterator vIt = residuals.begin();
std::list< Base::Vector3f >::const_iterator cIt;
for (cIt = _vPoints.begin(); cIt != _vPoints.end(); ++cIt, ++vIt)
{
// if (using this point) { // currently all given points are used (could modify this if eliminating outliers, etc....
++nPtsUsed;
Base::Vector3d &v = *vIt;
setupObservation(*cIt, v, a, f0, qw, b);
double qv = -f0;
for (int i = 0; i < 4; ++i)
qv += a[i] * x(i);
// We are using equal weights for sphere point coordinate observations (see setupObservation)
// i.e. w[0] = w[1] = w[2] = 1.0;
//double vx = -qw * qv * b[0] / w[0];
//double vy = -qw * qv * b[1] / w[1];
//double vz = -qw * qv * b[2] / w[2];
double vx = -qw * qv * b[0];
double vy = -qw * qv * b[1];
double vz = -qw * qv * b[2];
double dVx = fabs(vx - v.x);
double dVy = fabs(vy - v.y);
double dVz = fabs(vz - v.z);
v.x = vx;
v.y = vy;
v.z = vz;
//double vv = v.x * v.x + v.y * v.y + v.z * v.z;
//rmsVv += vv * vv;
//sigma0 += v.x * w[0] * v.x + v.y * w[1] * v.y + v.z * w[2] * v.z;
sigma0 += v.x * v.x + v.y * v.y + v.z * v.z;
if ((dVx > vConvLimit) || (dVy > vConvLimit) || (dVz > vConvLimit))
vConverged = false;
//if (dVx > maxdVx)
// maxdVx = dVx;
//if (dVy > maxdVy)
// maxdVy = dVy;
//if (dVz > maxdVz)
// maxdVz = dVz;
}
// Compute degrees of freedom and sigma0
if (nPtsUsed < 4) // A minimum of 4 surface points is needed to define a sphere
{
sigma0 = 0.0;
return false;
}
int df = nPtsUsed - 4;
if (df == 0)
sigma0 = 0.0;
else
sigma0 = sqrt (sigma0 / (double)df);
//rmsVv = sqrt(rmsVv / (double)nPtsUsed);
//Base::Console().Message("X: %0.3e %0.3e %0.3e %0.3e , Max dV: %0.4f %0.4f %0.4f , RMS Vv: %0.4f\n", x(0), x(1), x(2), x(3), maxdVx, maxdVy, maxdVz, rmsVv);
return true;
}
+130
View File
@@ -0,0 +1,130 @@
/***************************************************************************
* Copyright (c) 2020 Graeme van der Vlugt *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef MESH_SPHERE_FIT_H
#define MESH_SPHERE_FIT_H
#include "Approximation.h"
#include <Eigen/Eigenvalues>
// -------------------------------------------------------------------------------
namespace MeshCoreFit {
typedef Eigen::Matrix<double,4,4,Eigen::RowMajor> Matrix4x4;
/**
* Best-fit sphere for a given set of points.
*/
class MeshExport SphereFit : public MeshCore::Approximation
{
public:
/**
* Construction
*/
SphereFit();
/**
* Destruction
*/
virtual ~SphereFit();
/**
* Set approximations before calling Fit()
*/
void SetApproximations(double radius, const Base::Vector3d &center);
/**
* Set iteration convergence criteria for the fit if special values are needed.
* The default values set in the constructor are suitable for most uses
*/
void SetConvergenceCriteria(double posConvLimit, double vConvLimit, int maxIter);
/**
* Returns the radius of the fitted sphere. If Fit() has not been called then zero is returned.
*/
double GetRadius() const;
/**
* Returns the center of the fitted sphere. If Fit() has not been called the null vector is returned.
*/
Base::Vector3d GetCenter() const;
/**
* Returns the number of iterations that Fit() needed to converge. If Fit() has not been called then zero is returned.
*/
int GetNumIterations() const;
/**
* Compute approximations for the parameters using all points
*/
void ComputeApproximations();
/**
* Fit a sphere onto the given points. If the fit fails FLOAT_MAX is returned.
*/
float Fit();
/**
* Returns the distance from the point \a rcPoint to the fitted sphere. If Fit() has not been
* called FLOAT_MAX is returned.
*/
float GetDistanceToSphere(const Base::Vector3f &rcPoint) const;
/**
* Returns the standard deviation from the points to the fitted sphere. If Fit() has not been
* called FLOAT_MAX is returned.
*/
float GetStdDeviation() const;
/**
* Projects the points onto the fitted sphere.
*/
void ProjectToSphere();
protected:
/**
* Set up the normal equations
*/
void setupNormalEquationMatrices(const std::vector< Base::Vector3d > &residuals, Matrix4x4 &atpa, Eigen::VectorXd &atpl) const;
/**
* Sets up contributions of given observation to the normal equation matrices.
*/
void setupObservation(const Base::Vector3f &point, const Base::Vector3d &residual, double a[4], double &f0, double &qw, double b[3]) const;
/**
* Computes contribution of the given observation equation on the normal equation matrices
*/
void addObservationU(double a[4], double li, double pi, Matrix4x4 &atpa, Eigen::VectorXd &atpl) const;
/**
* Set the lower part of the normal matrix equal to the upper part
*/
void setLowerPart(Matrix4x4 &atpa) const;
/**
* Compute the residuals and sigma0 and check the residual convergence
*/
bool computeResiduals(const Eigen::VectorXd &x, std::vector< Base::Vector3d > &residuals, double &sigma0, double vConvLimit, bool &vConverged) const;
protected:
Base::Vector3d _vCenter;/**< Center of sphere. */
double _dRadius; /**< Radius of the sphere. */
int _numIter; /**< Number of iterations for solution to converge. */
double _posConvLimit; /**< Position and radius parameter convergence threshold. */
double _vConvLimit; /**< Residual convergence threshold. */
int _maxIter; /**< Maximum number of iterations. */
};
} // namespace MeshCore
#endif // MESH_SPHERE_FIT_H
+1 -1
View File
@@ -469,7 +469,7 @@ an empty dictionary if there is no intersection.
<UserDocu>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.</UserDocu>
plane if none of its neighbors are coplanar.</UserDocu>
</Documentation>
</Methode>
<Methode Name="getSegmentsOfType" Const="true">
+2 -1
View File
@@ -62,6 +62,7 @@
# include <Geom_Plane.hxx>
# include <Geom2d_TrimmedCurve.hxx>
# include <Interface_Static.hxx>
# include <Poly_Triangulation.hxx>
# include <ShapeUpgrade_ShellSewing.hxx>
# include <Standard_ConstructionError.hxx>
# include <Standard_DomainError.hxx>
@@ -749,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()));
+13 -4
View File
@@ -40,7 +40,7 @@
<property name="toolTip">
<string>The default color for new shapes</string>
</property>
<property name="color" stdset="0">
<property name="color">
<color>
<red>204</red>
<green>204</green>
@@ -89,7 +89,7 @@
<property name="toolTip">
<string>The default line color for new shapes</string>
</property>
<property name="color" stdset="0">
<property name="color">
<color>
<red>25</red>
<green>25</green>
@@ -157,7 +157,7 @@
<property name="toolTip">
<string>The default color for new vertices</string>
</property>
<property name="color" stdset="0">
<property name="color">
<color>
<red>25</red>
<green>25</green>
@@ -225,7 +225,7 @@
<property name="toolTip">
<string>The color of bounding boxes in the 3D view</string>
</property>
<property name="color" stdset="0">
<property name="color">
<color>
<red>255</red>
<green>255</green>
@@ -248,6 +248,12 @@
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Bottom side of surface will be rendered the same way than top.
If not checked, it depends on the option &quot;Backlight color&quot;
(preferences section Display -&gt; 3D View); either the backlight color
will be used or black.</string>
</property>
<property name="text">
<string>Two-side rendering</string>
</property>
@@ -303,6 +309,9 @@
</item>
<item row="0" column="1">
<widget class="Gui::PrefColorButton" name="AnnotationTextColor">
<property name="toolTip">
<string>Text color for document annotations</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>AnnotationTextColor</cstring>
</property>
+18 -1
View File
@@ -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)
@@ -37,6 +37,7 @@
<file>icons/PartDesign_Revolution.svg</file>
<file>icons/PartDesign_Scaled.svg</file>
<file>icons/PartDesign_ShapeBinder.svg</file>
<file>icons/PartDesign_Sprocket.svg</file>
<file>icons/PartDesign_SubShapeBinder.svg</file>
<file>icons/PartDesign_Subtractive_Box.svg</file>
<file>icons/PartDesign_Subtractive_Cone.svg</file>
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 89 KiB

+4 -3
View File
@@ -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
+1
View File
@@ -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:
+248
View File
@@ -0,0 +1,248 @@
#***************************************************************************
#* Copyright (c) 2020 Adam Spontarelli <[email protected]> *
#* *
#* 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())
+232
View File
@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SprocketParameter</class>
<widget class="QWidget" name="SprocketParameter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>195</width>
<height>142</height>
</rect>
</property>
<property name="windowTitle">
<string>Sprocket parameter</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Number of teeth:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_NumberOfTeeth">
<property name="minimum">
<number>3</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ANSI Size:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_ANSISize">
<item>
<property name="text">
<string>25</string>
</property>
</item>
<item>
<property name="text">
<string>35</string>
</property>
</item>
<item>
<property name="text">
<string>41</string>
</property>
</item>
<item>
<property name="text">
<string>40</string>
</property>
</item>
<item>
<property name="text">
<string>50</string>
</property>
</item>
<item>
<property name="text">
<string>60</string>
</property>
</item>
<item>
<property name="text">
<string>80</string>
</property>
</item>
<item>
<property name="text">
<string>100</string>
</property>
</item>
<item>
<property name="text">
<string>120</string>
</property>
</item>
<item>
<property name="text">
<string>140</string>
</property>
</item>
<item>
<property name="text">
<string>160</string>
</property>
</item>
<item>
<property name="text">
<string>180</string>
</property>
</item>
<item>
<property name="text">
<string>200</string>
</property>
</item>
<item>
<property name="text">
<string>240</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Chain Pitch:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::InputField" name="Quantity_Pitch">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="unit" stdset="0">
<string notr="true">in</string>
</property>
<property name="decimals" stdset="0">
<number>3</number>
</property>
<property name="maximum" stdset="0">
<double>2000.000000000000000</double>
</property>
<property name="minimum" stdset="0">
<double>0.01</double>
</property>
<property name="singleStep" stdset="0">
<double>0.001</double>
</property>
<property name="value" stdset="0">
<double>0.375</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Roller Diameter:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::InputField" name="Quantity_RollerDiameter">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>20</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="unit" stdset="0">
<string notr="true">in</string>
</property>
<property name="decimals" stdset="0">
<number>3</number>
</property>
<property name="maximum" stdset="0">
<double>50.000000000000000</double>
</property>
<property name="minimum" stdset="0">
<double>0.01</double>
</property>
<property name="singleStep" stdset="0">
<double>0.01</double>
</property>
<property name="value" stdset="0">
<double>0.20</double>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+24
View File
@@ -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 <adam@vector-space.org>.
Distributed under the LGPL licence.
@@ -0,0 +1 @@
+105
View File
@@ -0,0 +1,105 @@
# (c) 2014 David Douard <[email protected]>
#
# 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
@@ -0,0 +1,66 @@
# (c) 2020 Adam Spontarelli <[email protected]>
#
# 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_()
+139
View File
@@ -0,0 +1,139 @@
# (c) 2020 Adam Spontarelli <[email protected]>
#
# 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)
+2 -4
View File
@@ -1189,10 +1189,8 @@ static int foreachSubshape(const TopoDS_Shape &shape,
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
for(auto &s : openShapes) {
for(TopExp_Explorer it(s,TopAbs_EDGE); it.More(); it.Next())
builder.Add(comp,s);
}
for(auto &s : openShapes)
builder.Add(comp,s);
func(comp, TopAbs_COMPOUND);
return TopAbs_COMPOUND;
}

Some files were not shown because too many files have changed in this diff Show More