Merge branch 'master' of https://github.com/FreeCAD/FreeCAD into toolbit-gui
This commit is contained in:
+1
-1
@@ -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']
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Vendored
+2
@@ -2,3 +2,5 @@
|
||||
if (BUILD_SMESH AND NOT FREECAD_USE_EXTERNAL_SMESH)
|
||||
add_subdirectory(salomesmesh)
|
||||
endif()
|
||||
|
||||
add_subdirectory(lazy_loader)
|
||||
+12
@@ -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")
|
||||
|
||||
Vendored
+13
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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])
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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 "Link array" 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 "Link array" 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 "Link array" 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 "Link array" 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 "Link array" 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 "Link array" 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())
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ¢er)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
@@ -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 ¢er);
|
||||
/**
|
||||
* 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
|
||||
@@ -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">
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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 "Backlight color"
|
||||
(preferences section Display -> 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>
|
||||
|
||||
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
@@ -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>
|
||||
@@ -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 @@
|
||||
|
||||
@@ -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_()
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user