From 4d6f531aafb6541c4760731a3f850c80de469702 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 16:03:28 +0100 Subject: [PATCH 01/14] Gui: [skip ci] reduce size of image settings dialog --- src/Gui/DlgSettingsImage.ui | 369 +++++++++++++++----------------- src/Gui/DlgSettingsImageImp.cpp | 4 +- 2 files changed, 169 insertions(+), 204 deletions(-) diff --git a/src/Gui/DlgSettingsImage.ui b/src/Gui/DlgSettingsImage.ui index d47dd263bd..a06837cf41 100644 --- a/src/Gui/DlgSettingsImage.ui +++ b/src/Gui/DlgSettingsImage.ui @@ -6,8 +6,8 @@ 0 0 - 459 - 552 + 291 + 498 @@ -29,122 +29,6 @@ 6 - - - - false - - - Image comment - - - - 9 - - - 9 - - - 9 - - - 9 - - - 6 - - - - - Insert MIBA - - - true - - - - - - - false - - - - - - - Insert comment - - - - - - - Add watermark - - - - - - - - - - Image properties - - - - 9 - - - 9 - - - 9 - - - 9 - - - 6 - - - - - Back&ground: - - - comboBackground - - - - - - - - Current - - - - - White - - - - - Black - - - - - Transparent - - - - - - - @@ -183,57 +67,14 @@ 6 - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 1 - - - 32767 - - - - - - - 1 - - - 32767 - - - - - + + - Pixel + Standard sizes: - - - - &Width: - - - spinWidth - - - - + @@ -352,46 +193,33 @@ - - + + - Standard sizes: + &Width: + + + spinWidth - - - - Qt::Horizontal + + + + 1 - - - 40 - 20 - + + 32767 - + - - + + Pixel - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -402,6 +230,23 @@ + + + + 1 + + + 32767 + + + + + + + Pixel + + + @@ -429,7 +274,7 @@ - + &Screen @@ -439,7 +284,7 @@ - + &4:3 @@ -449,7 +294,7 @@ - + 1&6:9 @@ -459,7 +304,7 @@ - + &1:1 @@ -473,25 +318,144 @@ - - + + - Method + Image properties - + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + Back&ground: + + + comboBackground + + + + + + + + Current + + + + + White + + + + + Black + + + + + Transparent + + + + + Creation method: - + + + + + false + + + Image comment + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + Insert MIBA + + + true + + + + + + + Insert comment + + + + + + + false + + + + 0 + 0 + + + + + 0 + 70 + + + + + + + + Add watermark + + + + + + @@ -504,6 +468,7 @@ buttonRatio16x9 buttonRatio1x1 comboBackground + comboMethod radioButtonMiba radioButtonComment textEditComment diff --git a/src/Gui/DlgSettingsImageImp.cpp b/src/Gui/DlgSettingsImageImp.cpp index 1e981e9a19..126c4f5f03 100644 --- a/src/Gui/DlgSettingsImageImp.cpp +++ b/src/Gui/DlgSettingsImageImp.cpp @@ -252,10 +252,10 @@ void DlgSettingsImageImp::on_comboMethod_activated(int index) { QByteArray data = ui->comboMethod->itemData(index).toByteArray(); if (data == QByteArray("GrabFramebuffer")) { - ui->groupBoxProp->setEnabled(false); + ui->comboBackground->setEnabled(false); } else { - ui->groupBoxProp->setEnabled(true); + ui->comboBackground->setEnabled(true); } } From 1f9253588363a9c0e3d2b7b1c88fb74d5de876e8 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 17:07:04 +0100 Subject: [PATCH 02/14] PartDesign: [skip ci] set explicit encoding of Python files for backward compatibility --- src/Mod/PartDesign/WizardShaft/SegmentFunction.py | 1 + src/Mod/PartDesign/WizardShaft/ShaftDiagram.py | 1 + src/Mod/PartDesign/WizardShaft/ShaftFeature.py | 1 + src/Mod/PartDesign/WizardShaft/WizardShaft.py | 1 + src/Mod/PartDesign/WizardShaft/WizardShaftTable.py | 1 + src/Mod/PartDesign/WizardShaft/__init__.py | 1 + 6 files changed, 6 insertions(+) diff --git a/src/Mod/PartDesign/WizardShaft/SegmentFunction.py b/src/Mod/PartDesign/WizardShaft/SegmentFunction.py index 3aac232e44..5a9bdc7ee5 100644 --- a/src/Mod/PartDesign/WizardShaft/SegmentFunction.py +++ b/src/Mod/PartDesign/WizardShaft/SegmentFunction.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #/****************************************************************************** # * Copyright (c) 2012 Jan Rheinländer * # * * diff --git a/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py b/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py index 026e4d9683..b0fdf29f72 100644 --- a/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py +++ b/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #/****************************************************************************** # * Copyright (c) 2012 Jan Rheinländer * # * * diff --git a/src/Mod/PartDesign/WizardShaft/ShaftFeature.py b/src/Mod/PartDesign/WizardShaft/ShaftFeature.py index 9413ca1a35..82e9271335 100644 --- a/src/Mod/PartDesign/WizardShaft/ShaftFeature.py +++ b/src/Mod/PartDesign/WizardShaft/ShaftFeature.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #/****************************************************************************** # * Copyright (c) 2012 Jan Rheinländer * # * * diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaft.py b/src/Mod/PartDesign/WizardShaft/WizardShaft.py index 35d2b1db7e..e08da93870 100644 --- a/src/Mod/PartDesign/WizardShaft/WizardShaft.py +++ b/src/Mod/PartDesign/WizardShaft/WizardShaft.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #/****************************************************************************** # * Copyright (c) 2012 Jan Rheinländer * # * * diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py index f3dcb726c4..a37b205183 100644 --- a/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py +++ b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #/****************************************************************************** # * Copyright (c) 2012 Jan Rheinländer * # * * diff --git a/src/Mod/PartDesign/WizardShaft/__init__.py b/src/Mod/PartDesign/WizardShaft/__init__.py index d32e9e3c29..670cb443f4 100644 --- a/src/Mod/PartDesign/WizardShaft/__init__.py +++ b/src/Mod/PartDesign/WizardShaft/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Shaft Wizard """ From 222a84423d616923b3cc66f1a55002dea6add26f Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 17:23:12 +0100 Subject: [PATCH 03/14] Gui: [skip ci] avoid weird acronyms in function names --- src/Gui/QuantitySpinBox.cpp | 4 ++-- src/Gui/QuantitySpinBox.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index f359d0a266..a04484f8c8 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -290,12 +290,12 @@ void Gui::QuantitySpinBox::setExpression(boost::shared_ptr expr) } } -void Gui::QuantitySpinBox::setTooltipLE(const QString &name) +void Gui::QuantitySpinBox::setEditorToolTip(const QString &name) { lineEdit()->setToolTip(name); } -void Gui::QuantitySpinBox::setTooltipIL(const QString &name) +void Gui::QuantitySpinBox::setIconToolTip(const QString &name) { iconLabel->setToolTip(name); } diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index 0a4a2b5da7..1c851ea4be 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -130,9 +130,9 @@ public: void setExpression(boost::shared_ptr expr); /// Sets a tooltip for the LineEdit - void setTooltipLE(const QString &name); + void setEditorToolTip(const QString &name); /// Sets a tooltip for the IconLabel - void setTooltipIL(const QString &name); + void setIconToolTip(const QString &name); void bind(const App::ObjectIdentifier &_path); bool apply(const std::string &propName); From 4df8233ec910202affacdc3402b5f8ae0b6aa4f5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 17:28:31 +0100 Subject: [PATCH 04/14] Gui: [skip ci] avoid weird acronyms in function names --- src/Mod/Part/Gui/TaskAttacher.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index a4e988b5a6..d5333d1f36 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -218,9 +218,9 @@ TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject *ViewProvider,QWidget // set tooltips because the ones in the .ui file are not taken for QuantitySpinBoxes (see bug https://freecadweb.org/tracker/view.php?id=4059) // FIXME: remove this once the bug is fixed - ui->attachmentOffsetRoll->setTooltipLE(QString::fromUtf8("Rotation around the x-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); - ui->attachmentOffsetPitch->setTooltipLE(QString::fromUtf8("Rotation around the y-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); - ui->attachmentOffsetYaw->setTooltipLE(QString::fromUtf8("Rotation around the z-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); + ui->attachmentOffsetRoll->setEditorToolTip(QString::fromUtf8("Rotation around the x-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); + ui->attachmentOffsetPitch->setEditorToolTip(QString::fromUtf8("Rotation around the y-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); + ui->attachmentOffsetYaw->setEditorToolTip(QString::fromUtf8("Rotation around the z-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); } TaskAttacher::~TaskAttacher() From a03244fa1e035c8a2f8c146e7d6039c495c92648 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 21:35:13 +0100 Subject: [PATCH 05/14] Gui: [skip ci] when changing expression of a widget then set tooltip only for the label child but not for the widget itself this allows that a custom tooltip can be set --- src/Gui/QuantitySpinBox.cpp | 11 ++++------- src/Gui/SpinBox.cpp | 31 ++++++++++++------------------- src/Gui/Widgets.cpp | 10 ++++------ 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index a04484f8c8..8e419d0045 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -394,8 +394,7 @@ void Gui::QuantitySpinBox::onChange() p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - iconLabel->setToolTip(QString()); - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -406,7 +405,6 @@ void Gui::QuantitySpinBox::onChange() lineEdit()->setPalette(p); iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } @@ -460,7 +458,7 @@ void QuantitySpinBox::resizeEvent(QResizeEvent * event) p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -470,9 +468,8 @@ void QuantitySpinBox::resizeEvent(QResizeEvent * event) QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } catch (const Base::Exception & e) { setReadOnly(true); @@ -489,7 +486,7 @@ void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent *event) if (event->text() == QString::fromUtf8("=") && isBound()) openFormulaDialog(); else if (!hasExpression()) - QAbstractSpinBox::keyPressEvent(event); + QAbstractSpinBox::keyPressEvent(event); } diff --git a/src/Gui/SpinBox.cpp b/src/Gui/SpinBox.cpp index 0a0470cda0..c0d6114c29 100644 --- a/src/Gui/SpinBox.cpp +++ b/src/Gui/SpinBox.cpp @@ -292,7 +292,7 @@ void UIntSpinBox::onChange() { p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -300,9 +300,8 @@ void UIntSpinBox::onChange() { QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } @@ -344,7 +343,7 @@ void UIntSpinBox::resizeEvent(QResizeEvent * event) p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -354,9 +353,8 @@ void UIntSpinBox::resizeEvent(QResizeEvent * event) QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } catch (const Base::Exception & e) { setReadOnly(true); @@ -489,7 +487,7 @@ void IntSpinBox::onChange() { p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -497,9 +495,8 @@ void IntSpinBox::onChange() { QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } void IntSpinBox::resizeEvent(QResizeEvent * event) @@ -525,7 +522,7 @@ void IntSpinBox::resizeEvent(QResizeEvent * event) p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -535,9 +532,8 @@ void IntSpinBox::resizeEvent(QResizeEvent * event) QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } catch (const Base::Exception & e) { setReadOnly(true); @@ -670,7 +666,7 @@ void DoubleSpinBox::onChange() { p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -678,9 +674,8 @@ void DoubleSpinBox::onChange() { QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } void DoubleSpinBox::resizeEvent(QResizeEvent * event) @@ -706,7 +701,7 @@ void DoubleSpinBox::resizeEvent(QResizeEvent * event) p.setColor(QPalette::Text, Qt::lightGray); lineEdit()->setPalette(p); } - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -716,9 +711,8 @@ void DoubleSpinBox::resizeEvent(QResizeEvent * event) QPalette p(lineEdit()->palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); lineEdit()->setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } catch (const Base::Exception & e) { setReadOnly(true); @@ -727,7 +721,6 @@ void DoubleSpinBox::resizeEvent(QResizeEvent * event) lineEdit()->setPalette(p); iconLabel->setToolTip(QString::fromLatin1(e.what())); } - } void DoubleSpinBox::openFormulaDialog() diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index b760d2da4e..fb331c4550 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -1489,7 +1489,7 @@ void ExpLineEdit::onChange() { QPalette p(palette()); p.setColor(QPalette::Text, Qt::lightGray); setPalette(p); - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -1497,9 +1497,8 @@ void ExpLineEdit::onChange() { QPalette p(palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } void ExpLineEdit::resizeEvent(QResizeEvent * event) @@ -1520,7 +1519,7 @@ void ExpLineEdit::resizeEvent(QResizeEvent * event) QPalette p(palette()); p.setColor(QPalette::Text, Qt::lightGray); setPalette(p); - setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + iconLabel->setToolTip(Base::Tools::fromStdString(getExpression()->toString())); } else { setReadOnly(false); @@ -1530,9 +1529,8 @@ void ExpLineEdit::resizeEvent(QResizeEvent * event) QPalette p(palette()); p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); setPalette(p); - + iconLabel->setToolTip(QString()); } - iconLabel->setToolTip(QString()); } catch (const Base::Exception & e) { setReadOnly(true); From d700744052d133f426172243d4eef6ea948e3175 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 22:39:47 +0100 Subject: [PATCH 06/14] fixes 0004059: Tooltips for Gui::QuantitySpinBox not showing [skip ci] --- src/Gui/QuantitySpinBox.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 8e419d0045..b2c5d1475e 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -850,6 +850,16 @@ void QuantitySpinBox::showEvent(QShowEvent * event) bool QuantitySpinBox::event(QEvent * event) { + // issue #0004059: Tooltips for Gui::QuantitySpinBox not showing + // Here we must not try to show the tooltip of the icon label + // because it would override a custom tooltip set to this widget. + // + // We could also check if the text of this tooltip is empty but + // it will fail in cases where the widget is embedded into the + // property editor and the corresponding item has set a tooltip. + // Instead of showing the item's tooltip it will again show the + // tooltip of the icon label. +#if 0 if (event->type() == QEvent::ToolTip) { if (isBound() && getExpression() && lineEdit()->isReadOnly()) { QHelpEvent * helpEvent = static_cast(event); @@ -859,6 +869,7 @@ bool QuantitySpinBox::event(QEvent * event) return true; } } +#endif return QAbstractSpinBox::event(event); } From 379018cde472e70de44bedab64fc29f86ace685c Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 9 Jan 2020 23:36:28 +0100 Subject: [PATCH 07/14] [skip ci] fix superfluous workarounds --- src/Gui/QuantitySpinBox.cpp | 10 ---------- src/Gui/QuantitySpinBox.h | 4 ---- src/Mod/Part/Gui/TaskAttacher.cpp | 16 ++++++---------- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index b2c5d1475e..3bfd2a2db6 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -290,16 +290,6 @@ void Gui::QuantitySpinBox::setExpression(boost::shared_ptr expr) } } -void Gui::QuantitySpinBox::setEditorToolTip(const QString &name) -{ - lineEdit()->setToolTip(name); -} - -void Gui::QuantitySpinBox::setIconToolTip(const QString &name) -{ - iconLabel->setToolTip(name); -} - QString QuantitySpinBox::boundToName() const { if (isBound()) { diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index 1c851ea4be..cc9c9ddaea 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -129,10 +129,6 @@ public: bool event(QEvent *event); void setExpression(boost::shared_ptr expr); - /// Sets a tooltip for the LineEdit - void setEditorToolTip(const QString &name); - /// Sets a tooltip for the IconLabel - void setIconToolTip(const QString &name); void bind(const App::ObjectIdentifier &_path); bool apply(const std::string &propName); diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index d5333d1f36..4668f3036f 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -215,12 +215,6 @@ TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject *ViewProvider,QWidget Gui::Document* document = Gui::Application::Instance->getDocument(ViewProvider->getObject()->getDocument()); connectDelObject = document->signalDeletedObject.connect(bnd1); connectDelDocument = document->signalDeleteDocument.connect(bnd2); - - // set tooltips because the ones in the .ui file are not taken for QuantitySpinBoxes (see bug https://freecadweb.org/tracker/view.php?id=4059) - // FIXME: remove this once the bug is fixed - ui->attachmentOffsetRoll->setEditorToolTip(QString::fromUtf8("Rotation around the x-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); - ui->attachmentOffsetPitch->setEditorToolTip(QString::fromUtf8("Rotation around the y-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); - ui->attachmentOffsetYaw->setEditorToolTip(QString::fromUtf8("Rotation around the z-axis\nNote: The placement is expressed in local coordinate system\nof object being attached.")); } TaskAttacher::~TaskAttacher() @@ -745,10 +739,12 @@ void TaskAttacher::updateAttachmentOffsetUI() ui->attachmentOffsetPitch->setEnabled(!bRotationBound); ui->attachmentOffsetRoll->setEnabled(!bRotationBound); - QString tooltip = bRotationBound ? tr("Not editable because rotation of AttachmentOffset is bound by expressions.") : QString(); - ui->attachmentOffsetYaw->setToolTip(tooltip); - ui->attachmentOffsetPitch->setToolTip(tooltip); - ui->attachmentOffsetRoll->setToolTip(tooltip); + if (bRotationBound) { + QString tooltip = tr("Not editable because rotation of AttachmentOffset is bound by expressions."); + ui->attachmentOffsetYaw->setToolTip(tooltip); + ui->attachmentOffsetPitch->setToolTip(tooltip); + ui->attachmentOffsetRoll->setToolTip(tooltip); + } bBlock = false; ui->attachmentOffsetX->blockSignals(bBlock); From 683fbffe4a00629b256410ab0ed76bafa195a8c3 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 10 Jan 2020 09:01:21 +0100 Subject: [PATCH 08/14] FEM: frd result reading, improve error handling --- src/Mod/Fem/feminout/importCcxFrdResults.py | 31 +++++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index 0a4bb2b4b8..81917224d4 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -74,6 +74,8 @@ def importFrd( m = read_frd_result(filename) result_mesh_object = None + res_obj = None + if len(m["Nodes"]) > 0: mesh = importToolsFem.make_femmesh(m) result_mesh_object = ObjectsFem.makeMeshResult( @@ -166,15 +168,29 @@ def importFrd( else: error_message = ( - "We have nodes but no results in frd file, " - "which means we only have a mesh in frd file. " - "Usually this happens for analysis type 'NOANALYSIS' " - "or if CalculiX returned no results because " - "of nonpositive jacobian determinant in at least one element.\n" + "Nodes, but no results found in frd file. " + "It means there only is a mesh but no results in frd file. " + "Usually this happens for: \n" + "- analysis type 'NOANALYSIS'\n" + "- if CalculiX returned no results " + "(happens on nonpositive jacobian determinant in at least one element)\n" + "- just no frd results where requestet in input file " + "(neither 'node file' nor 'el file' in output section')\n" ) Console.PrintMessage(error_message) + + # create a result obj, even if we have no results but a result mesh in frd file + # see error message above for more information + if not res_obj: + if result_name_prefix: + results_name = ("{}_Results".format(result_name_prefix)) + else: + results_name = ("Results".format(result_name_prefix)) + res_obj = ObjectsFem.makeResultMechanical(FreeCAD.ActiveDocument, results_name) + res_obj.Mesh = result_mesh_object + # TODO, node numbers in result obj could be set if analysis: - analysis.addObject(result_mesh_object) + analysis.addObject(res_obj) if FreeCAD.GuiUp: if analysis: @@ -186,6 +202,9 @@ def importFrd( Console.PrintError( "Problem on frd file import. No nodes found in frd file.\n" ) + # None will be returned + # or would it be better to raise an exception if there are not even nodes in frd file + return res_obj From d7d88da29181d7dbd4b69cc168386448539ef593 Mon Sep 17 00:00:00 2001 From: Zackles Date: Thu, 9 Jan 2020 18:25:25 +0100 Subject: [PATCH 09/14] Add Proxy to Addon Manager with UI Correct crash when Macro description is not reachable Python crashed when under proxy - Macro list is charged - Proxy is undone ( internet connection lost) - Try to load macro description (u=None) Add Proxy setting in Addon Manager Option UI Correction of SSL context Replace ssl.Purpose.CLIENT_AUTH by ssl.Purpose.SERVER_AUTH as context for a client Add configuration of proxy setting UI Add proxy management by urllib --- src/Mod/AddonManager/AddonManager.py | 8 +++ src/Mod/AddonManager/AddonManagerOptions.ui | 58 ++++++++++++++++--- src/Mod/AddonManager/addonmanager_macro.py | 3 + .../AddonManager/addonmanager_utilities.py | 35 ++++++++--- 4 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 08342febc6..3bd5f36d47 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -589,6 +589,10 @@ class CommandAddonManager: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") self.config.checkUpdates.setChecked(pref.GetBool("AutoCheck",False)) self.config.customRepositories.setPlainText(pref.GetString("CustomRepositories","")) + self.config.radioButtonNoProxy.setChecked(pref.GetBool("NoProxyCheck",True)) + self.config.radioButtonSystemProxy.setChecked(pref.GetBool("SystemProxyCheck",False)) + self.config.radioButtonUserProxy.setChecked(pref.GetBool("UserProxyCheck",False)) + self.config.userProxy.setPlainText(pref.GetString("ProxyUrl","")) # center the dialog over the Addon Manager self.config.move(self.dialog.frameGeometry().topLeft() + self.dialog.rect().center() - self.config.rect().center()) @@ -599,6 +603,10 @@ class CommandAddonManager: # OK button has been pressed pref.SetBool("AutoCheck",self.config.checkUpdates.isChecked()) pref.SetString("CustomRepositories",self.config.customRepositories.toPlainText()) + pref.SetBool("NoProxyCheck",self.config.radioButtonNoProxy.isChecked()) + pref.SetBool("SystemProxyCheck",self.config.radioButtonSystemProxy.isChecked()) + pref.SetBool("UserProxyCheck",self.config.radioButtonUserProxy.isChecked()) + pref.SetString("ProxyUrl",self.config.userProxy.toPlainText()) def check_updates(addon_name,callback): diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index cb88650cd6..709e222fc4 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -7,7 +7,7 @@ 0 0 390 - 183 + 247 @@ -24,6 +24,9 @@ installed addons will be checked for available updates Automatically check for updates at start (requires GitPython) + + false + @@ -41,6 +44,47 @@ sto be scanned for available addons + + + + Qt::Horizontal + + + + + + + Proxy + + + + + + + No proxy + + + + + + + User system proxy + + + + + + + User defined proxy : + + + true + + + + + + @@ -62,12 +106,12 @@ sto be scanned for available addons accept() - 248 - 254 + 257 + 237 157 - 274 + 246 @@ -78,12 +122,12 @@ sto be scanned for available addons reject() - 316 - 260 + 325 + 237 286 - 274 + 246 diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index c23e5e5319..e7039ff500 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -103,6 +103,9 @@ class Macro(object): except: print("AddonManager: Debug: unable to open URL",url) return + if u is None : + print("AddonManager: Debug: connection is lost (proxy setting changed?)",url) + return p = u.read() if sys.version_info.major >= 3 and isinstance(p, bytes): p = p.decode('utf-8') diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 037e22124b..d428478f11 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -40,12 +40,12 @@ except ImportError: pass else: try: - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) except AttributeError: pass + - - + def translate(context, text, disambig=None): "Main translation function" @@ -90,21 +90,38 @@ def urlopen(url): timeout = 5 if sys.version_info.major < 3: - import urllib2 + import urllib2 else: import urllib.request as urllib2 + + # Proxy an ssl configuration + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") + if pref.GetBool("NoProxyCheck",True): + proxies = {} + else: + if pref.GetBool("SystemProxyCheck",False): + proxy = urllib2.getproxies() + proxies = {"http": proxy.get('http'),"https": proxy.get('http')} + elif pref.GetBool("UserProxyCheck",False): + proxy = pref.GetString("ProxyUrl","") + proxies = {"http": proxy, "https": proxy} + if ssl_ctx: + handler = urllib2.HTTPSHandler(context=ssl_ctx) + else: + handler = {} + proxy_support = urllib2.ProxyHandler(proxies) + opener = urllib2.build_opener(proxy_support, handler) + urllib2.install_opener(opener) + + # Url opening try: - if ssl_ctx: - u = urllib2.urlopen(url, context=ssl_ctx, timeout=timeout) - else: - u = urllib2.urlopen(url, timeout=timeout) + u = urllib2.urlopen(url, timeout=timeout) except: return None else: return u - def getserver(url): """returns the server part of an url""" From cd764c6ddb95844a5bf315a1877313ef09b5a66f Mon Sep 17 00:00:00 2001 From: zacl Date: Thu, 9 Jan 2020 21:44:05 +0100 Subject: [PATCH 10/14] Remove trailing whitespace --- src/Mod/AddonManager/addonmanager_utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index d428478f11..799cb64f36 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -90,7 +90,7 @@ def urlopen(url): timeout = 5 if sys.version_info.major < 3: - import urllib2 + import urllib2 else: import urllib.request as urllib2 From ce2441c0b243383a0b5a485bc9e497f2e239aa88 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 9 Nov 2019 15:50:45 -0600 Subject: [PATCH 11/14] Draft: new module for GUI utility functions Many auxiliary tools used by `Draft.py` can be defined in another module. Many functions are moved to this module so that `Draft.py` isn't as big and hard to maintain. The following is a list o functions that were moved: `get3DView`, `autogroup`, `dimSymbol`, `dimDash`, `removeHidden`, `formatObject`, `getSelection`, `getSelectionEx`, `select`, `loadTexture`. Moreover, many of these functions were renamed to comply better with PEP8 guidelines, particularly the use of `snake_case`. For example, `get3DView` is now `get_3d_view`. Aliases are provided for the old names so that other functions and classes that depend on these won't break. The new names should be the official programming interface, while the old names should be deprecated at some point in the future. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/Draft.py | 280 +----------- src/Mod/Draft/draftutils/gui_utils.py | 612 ++++++++++++++++++++++++++ 3 files changed, 633 insertions(+), 260 deletions(-) create mode 100644 src/Mod/Draft/draftutils/gui_utils.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index ebc882625d..5d7476dcd6 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -50,6 +50,7 @@ SET(Draft_tests SET(Draft_utilities draftutils/__init__.py draftutils/utils.py + draftutils/gui_utils.py ) SET(Draft_objects diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 09ecea6176..bb903e40e3 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -80,6 +80,8 @@ makeLayer = DraftLayer.makeLayer # General functions #--------------------------------------------------------------------------- import draftutils.utils +import draftutils.gui_utils + arrowtypes = draftutils.utils.ARROW_TYPES stringencodecoin = draftutils.utils.string_encode_coin @@ -110,20 +112,8 @@ get_type = draftutils.utils.get_type getObjectsOfType = draftutils.utils.get_objects_of_type get_objects_of_type = draftutils.utils.get_objects_of_type - -def get3DView(): - """get3DView(): returns the current view if it is 3D, or the first 3D view found, or None""" - if FreeCAD.GuiUp: - import FreeCADGui - v = FreeCADGui.ActiveDocument.ActiveView - if "View3DInventor" in str(type(v)): - return v - #print("Debug: Draft: Warning, not working in active view") - v = FreeCADGui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") - if v: - return v[0] - return None - +get3DView = draftutils.gui_utils.get_3d_view +get_3d_view = draftutils.gui_utils.get_3d_view isClone = draftutils.utils.is_clone is_clone = draftutils.utils.is_clone @@ -133,98 +123,21 @@ get_group_names = draftutils.utils.get_group_names ungroup = draftutils.utils.ungroup +autogroup = draftutils.gui_utils.autogroup -def autogroup(obj): - """adds a given object to the autogroup, if applicable""" - if FreeCAD.GuiUp: - if hasattr(FreeCADGui,"draftToolBar"): - if hasattr(FreeCADGui.draftToolBar,"autogroup") and (not FreeCADGui.draftToolBar.isConstructionMode()): - if FreeCADGui.draftToolBar.autogroup != None: - g = FreeCAD.ActiveDocument.getObject(FreeCADGui.draftToolBar.autogroup) - if g: - found = False - for o in g.Group: - if o.Name == obj.Name: - found = True - if not found: - gr = g.Group - gr.append(obj) - g.Group = gr - else: - # Arch active container - a = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") - if a: - a.addObject(obj) - -def dimSymbol(symbol=None,invert=False): - """returns the current dim symbol from the preferences as a pivy SoMarkerSet""" - if symbol is None: - symbol = getParam("dimsymbol",0) - from pivy import coin - if symbol == 0: - return coin.SoSphere() - elif symbol == 1: - marker = coin.SoMarkerSet() - marker.markerIndex = FreeCADGui.getMarkerIndex("circle", 9) - return marker - elif symbol == 2: - marker = coin.SoSeparator() - t = coin.SoTransform() - t.translation.setValue((0,-2,0)) - t.center.setValue((0,2,0)) - if invert: - t.rotation.setValue(coin.SbVec3f((0,0,1)),-math.pi/2) - else: - t.rotation.setValue(coin.SbVec3f((0,0,1)),math.pi/2) - c = coin.SoCone() - c.height.setValue(4) - marker.addChild(t) - marker.addChild(c) - return marker - elif symbol == 3: - marker = coin.SoSeparator() - c = coin.SoCoordinate3() - c.point.setValues([(-1,-2,0),(0,2,0),(1,2,0),(0,-2,0)]) - f = coin.SoFaceSet() - marker.addChild(c) - marker.addChild(f) - return marker - elif symbol == 4: - return dimDash((-1.5,-1.5,0),(1.5,1.5,0)) - else: - print("Draft.dimsymbol: Not implemented") - return coin.SoSphere() - -def dimDash(p1, p2): - """dimDash(p1, p2): returns pivy SoSeparator. - Used for making Tick-2, DimOvershoot, ExtOvershoot dashes. - """ - from pivy import coin - dash = coin.SoSeparator() - v = coin.SoVertexProperty() - v.vertex.set1Value(0, p1) - v.vertex.set1Value(1, p2) - l = coin.SoLineSet() - l.vertexProperty = v - dash.addChild(l) - return dash +dimSymbol = draftutils.gui_utils.dim_symbol +dim_symbol = draftutils.gui_utils.dim_symbol +dimDash = draftutils.gui_utils.dim_dash +dim_dash = draftutils.gui_utils.dim_dash shapify = draftutils.utils.shapify getGroupContents = draftutils.utils.get_group_contents get_group_contents = draftutils.utils.get_group_contents - -def removeHidden(objectslist): - """removeHidden(objectslist): removes hidden objects from the list""" - newlist = objectslist[:] - for o in objectslist: - if o.ViewObject: - if not o.ViewObject.isVisible(): - newlist.remove(o) - return newlist - +removeHidden = draftutils.gui_utils.remove_hidden +remove_hidden = draftutils.gui_utils.remove_hidden printShape = draftutils.utils.print_shape print_shape = draftutils.utils.print_shape @@ -232,91 +145,16 @@ print_shape = draftutils.utils.print_shape compareObjects = draftutils.utils.compare_objects compare_objects = draftutils.utils.compare_objects +formatObject = draftutils.gui_utils.format_object +format_object = draftutils.gui_utils.format_object -def formatObject(target,origin=None): - """ - formatObject(targetObject,[originObject]): This function applies - to the given target object the current properties - set on the toolbar (line color and line width), - or copies the properties of another object if given as origin. - It also places the object in construction group if needed. - """ - if not target: - return - obrep = target.ViewObject - if not obrep: - return - ui = None - if gui: - if hasattr(FreeCADGui,"draftToolBar"): - ui = FreeCADGui.draftToolBar - if ui: - doc = FreeCAD.ActiveDocument - if ui.isConstructionMode(): - col = fcol = ui.getDefaultColor("constr") - gname = getParam("constructiongroupname","Construction") - grp = doc.getObject(gname) - if not grp: - grp = doc.addObject("App::DocumentObjectGroup",gname) - grp.addObject(target) - if hasattr(obrep,"Transparency"): - obrep.Transparency = 80 - else: - col = ui.getDefaultColor("ui") - fcol = ui.getDefaultColor("face") - col = (float(col[0]),float(col[1]),float(col[2]),0.0) - fcol = (float(fcol[0]),float(fcol[1]),float(fcol[2]),0.0) - lw = ui.linewidth - fs = ui.fontsize - if not origin or not hasattr(origin,'ViewObject'): - if "FontSize" in obrep.PropertiesList: obrep.FontSize = fs - if "TextColor" in obrep.PropertiesList: obrep.TextColor = col - if "LineWidth" in obrep.PropertiesList: obrep.LineWidth = lw - if "PointColor" in obrep.PropertiesList: obrep.PointColor = col - if "LineColor" in obrep.PropertiesList: obrep.LineColor = col - if "ShapeColor" in obrep.PropertiesList: obrep.ShapeColor = fcol - else: - matchrep = origin.ViewObject - for p in matchrep.PropertiesList: - if not p in ["DisplayMode","BoundingBox","Proxy","RootNode","Visibility"]: - if p in obrep.PropertiesList: - if not obrep.getEditorMode(p): - if hasattr(getattr(matchrep,p),"Value"): - val = getattr(matchrep,p).Value - else: - val = getattr(matchrep,p) - try: - setattr(obrep,p,val) - except Exception: - pass - if matchrep.DisplayMode in obrep.listDisplayModes(): - obrep.DisplayMode = matchrep.DisplayMode - if hasattr(matchrep,"DiffuseColor") and hasattr(obrep,"DiffuseColor"): - obrep.DiffuseColor = matchrep.DiffuseColor +getSelection = draftutils.gui_utils.get_selection +get_selection = draftutils.gui_utils.get_selection -def getSelection(): - """getSelection(): returns the current FreeCAD selection""" - if gui: - return FreeCADGui.Selection.getSelection() - return None - -def getSelectionEx(): - """getSelectionEx(): returns the current FreeCAD selection (with subobjects)""" - if gui: - return FreeCADGui.Selection.getSelectionEx() - return None - -def select(objs=None): - """select(object): deselects everything and selects only the passed object or list""" - if gui: - FreeCADGui.Selection.clearSelection() - if objs: - if not isinstance(objs,list): - objs = [objs] - for obj in objs: - if obj: - FreeCADGui.Selection.addSelection(obj) +getSelectionEx = draftutils.gui_utils.get_selection_ex +get_selection_ex = draftutils.gui_utils.get_selection_ex +select = draftutils.gui_utils.select loadSvgPatterns = draftutils.utils.load_svg_patterns load_svg_patterns = draftutils.utils.load_svg_patterns @@ -324,86 +162,8 @@ load_svg_patterns = draftutils.utils.load_svg_patterns svgpatterns = draftutils.utils.svg_patterns svg_patterns = draftutils.utils.svg_patterns - -def loadTexture(filename,size=None): - """loadTexture(filename,[size]): returns a SoSFImage from a file. If size - is defined (an int or a tuple), and provided the input image is a png file, - it will be scaled to match the given size.""" - if gui: - from pivy import coin - from PySide import QtGui,QtSvg - try: - p = QtGui.QImage(filename) - # buggy - TODO: allow to use resolutions - #if size and (".svg" in filename.lower()): - # # this is a pattern, not a texture - # if isinstance(size,int): - # size = (size,size) - # svgr = QtSvg.QSvgRenderer(filename) - # p = QtGui.QImage(size[0],size[1],QtGui.QImage.Format_ARGB32) - # pa = QtGui.QPainter() - # pa.begin(p) - # svgr.render(pa) - # pa.end() - #else: - # p = QtGui.QImage(filename) - size = coin.SbVec2s(p.width(), p.height()) - buffersize = p.byteCount() - numcomponents = int (float(buffersize) / ( size[0] * size[1] )) - - img = coin.SoSFImage() - width = size[0] - height = size[1] - byteList = [] - isPy2 = sys.version_info.major < 3 - - for y in range(height): - #line = width*numcomponents*(height-(y)); - for x in range(width): - rgb = p.pixel(x,y) - if numcomponents == 1: - if isPy2: - byteList.append(chr(QtGui.qGray( rgb ))) - else: - byteList.append(chr(QtGui.qGray( rgb )).encode('latin-1')) - elif numcomponents == 2: - if isPy2: - byteList.append(chr(QtGui.qGray( rgb ))) - byteList.append(chr(QtGui.qAlpha( rgb ))) - else: - byteList.append(chr(QtGui.qGray( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qAlpha( rgb )).encode('latin-1')) - elif numcomponents == 3: - if isPy2: - byteList.append(chr(QtGui.qRed( rgb ))) - byteList.append(chr(QtGui.qGreen( rgb ))) - byteList.append(chr(QtGui.qBlue( rgb ))) - else: - byteList.append(chr(QtGui.qRed( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qGreen( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qBlue( rgb )).encode('latin-1')) - elif numcomponents == 4: - if isPy2: - byteList.append(chr(QtGui.qRed( rgb ))) - byteList.append(chr(QtGui.qGreen( rgb ))) - byteList.append(chr(QtGui.qBlue( rgb ))) - byteList.append(chr(QtGui.qAlpha( rgb ))) - else: - byteList.append(chr(QtGui.qRed( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qGreen( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qBlue( rgb )).encode('latin-1')) - byteList.append(chr(QtGui.qAlpha( rgb )).encode('latin-1')) - #line += numcomponents - - bytes = b"".join(byteList) - img.setValue(size, numcomponents, bytes) - except: - print("Draft: unable to load texture") - return None - else: - return img - return None - +loadTexture = draftutils.gui_utils.load_texture +load_texture = draftutils.gui_utils.load_texture getMovableChildren = draftutils.utils.get_movable_children get_movable_children = draftutils.utils.get_movable_children diff --git a/src/Mod/Draft/draftutils/gui_utils.py b/src/Mod/Draft/draftutils/gui_utils.py new file mode 100644 index 0000000000..6f1cbb7ce2 --- /dev/null +++ b/src/Mod/Draft/draftutils/gui_utils.py @@ -0,0 +1,612 @@ +"""This module provides GUI utility functions for the Draft Workbench. + +This module should contain auxiliary functions which require +the graphical user interface (GUI). +""" +## @package gui_utils +# \ingroup DRAFT +# \brief This module provides utility functions for the Draft Workbench + +# *************************************************************************** +# * (c) 2009, 2010 * +# * Yorik van Havre , Ken Cline * +# * (c) 2019 Eliud Cabrera Castillo * +# * * +# * 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 * +# * * +# *************************************************************************** + + +import FreeCAD +import FreeCADGui +from .utils import _msg +from .utils import _wrn +# from .utils import _log +from .utils import _tr +from .utils import getParam +from pivy import coin +from PySide import QtGui +# from PySide import QtSvg # for load_texture +import os +import math +import six + + +def get_3d_view(): + """Return the current 3D view. + + Returns + ------- + Gui::View3DInventor + Return the current `ActiveView` in the active document, + or the first `Gui::View3DInventor` view found. + + Return `None` if the graphical interface is not available. + """ + if FreeCAD.GuiUp: + v = FreeCADGui.ActiveDocument.ActiveView + if "View3DInventor" in str(type(v)): + return v + + # print("Debug: Draft: Warning, not working in active view") + v = FreeCADGui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") + if v: + return v[0] + + _wrn(_tr("No graphical interface")) + return None + + +get3DView = get_3d_view + + +def autogroup(obj): + """Adds a given object to the defined Draft autogroup, if applicable. + + This function only works if the graphical interface is available. + It checks that the `FreeCAD.draftToolBar` class is available, + which contains the group to use to automatically store + new created objects. + + Originally, it worked with standard groups (`App::DocumentObjectGroup`), + and Arch Workbench containers like `'Site'`, `'Building'`, `'Floor'`, + and `'BuildingPart'`. + + Now it works with Draft Layers. + + Parameters + ---------- + obj : App::DocumentObject + Any type of object that will be stored in the group. + """ + doc = FreeCAD.ActiveDocument + if FreeCAD.GuiUp: + view = FreeCADGui.ActiveDocument.ActiveView + if hasattr(FreeCADGui, "draftToolBar"): + if (hasattr(FreeCADGui.draftToolBar, "autogroup") + and not FreeCADGui.draftToolBar.isConstructionMode()): + if FreeCADGui.draftToolBar.autogroup is not None: + g = doc.getObject(FreeCADGui.draftToolBar.autogroup) + if g: + found = False + for o in g.Group: + if o.Name == obj.Name: + found = True + if not found: + gr = g.Group + gr.append(obj) + g.Group = gr + else: + # Arch active container + a = view.getActiveObject("Arch") + if a: + a.addObject(obj) + + +def dim_symbol(symbol=None, invert=False): + """Return the specified dimension symbol. + + Parameters + ---------- + symbol : int, optional + It defaults to `None`, in which it gets the value from the parameter + database, `get_param("dimsymbol", 0)`. + + A numerical value defines different markers + * 0, `SoSphere` + * 1, `SoMarkerSet` with a circle + * 2, `SoSeparator` with a `soCone` + * 3, `SoSeparator` with a `SoFaceSet` + * 4, `SoSeparator` with a `SoLineSet`, calling `dim_dash` + * Otherwise, `SoSphere` + + invert : bool, optional + It defaults to `False`. + If it is `True` and `symbol=2`, the cone will be rotated + -90 degrees around the Z axis, otherwise the rotation is positive, + +90 degrees. + + Returns + ------- + Coin.SoNode + A `Coin.SoSphere`, or `Coin.SoMarkerSet` (circle), + or `Coin.SoSeparator` (cone, face, line) + that will be used as a dimension symbol. + """ + if symbol is None: + symbol = getParam("dimsymbol", 0) + + if symbol == 0: + return coin.SoSphere() + elif symbol == 1: + marker = coin.SoMarkerSet() + marker.markerIndex = FreeCADGui.getMarkerIndex("circle", 9) + return marker + elif symbol == 2: + marker = coin.SoSeparator() + t = coin.SoTransform() + t.translation.setValue((0, -2, 0)) + t.center.setValue((0, 2, 0)) + if invert: + t.rotation.setValue(coin.SbVec3f((0, 0, 1)), -math.pi/2) + else: + t.rotation.setValue(coin.SbVec3f((0, 0, 1)), math.pi/2) + c = coin.SoCone() + c.height.setValue(4) + marker.addChild(t) + marker.addChild(c) + return marker + elif symbol == 3: + marker = coin.SoSeparator() + c = coin.SoCoordinate3() + c.point.setValues([(-1, -2, 0), (0, 2, 0), + (1, 2, 0), (0, -2, 0)]) + f = coin.SoFaceSet() + marker.addChild(c) + marker.addChild(f) + return marker + elif symbol == 4: + return dimDash((-1.5, -1.5, 0), (1.5, 1.5, 0)) + else: + _wrn(_tr("Symbol not implemented. Use a default symbol.")) + return coin.SoSphere() + + +dimSymbol = dim_symbol + + +def dim_dash(p1, p2): + """Return a SoSeparator with a line used to make dimension dashes. + + It is used by `dim_symbol` to create line end symbols + like `'Tick-2'`, `'DimOvershoot'`, and `'ExtOvershoot'` dashes. + + Parameters + ---------- + p1 : tuple of three floats or Base::Vector3 + A point to define a line vertex. + + p2 : tuple of three floats or Base::Vector3 + A point to define a line vertex. + + Returns + ------- + Coin.SoSeparator + A Coin object with a `SoLineSet` created from `p1` and `p2` + as vertices. + """ + dash = coin.SoSeparator() + v = coin.SoVertexProperty() + v.vertex.set1Value(0, p1) + v.vertex.set1Value(1, p2) + line = coin.SoLineSet() + line.vertexProperty = v + dash.addChild(line) + return dash + + +dimDash = dim_dash + + +def remove_hidden(objectslist): + """Return only the visible objects in the list. + + This function only works if the graphical interface is available + as the `Visibility` attribute is a property of the view provider + (`obj.ViewObject`). + + Parameters + ---------- + objectslist : list of App::DocumentObject + List of any type of object. + + Returns + ------- + list + Return a copy of the input list without those objects + for which `obj.ViewObject.Visibility` is `False`. + + If the graphical interface is not loaded + the returned list is just a copy of the input list. + """ + newlist = objectslist[:] + for obj in objectslist: + if obj.ViewObject: + if not obj.ViewObject.isVisible(): + newlist.remove(obj) + _msg(_tr("Visibility off; removed from list: ") + obj.Label) + return newlist + + +removeHidden = remove_hidden + + +def format_object(target, origin=None): + """Apply visual properties from the Draft toolbar or another object. + + This function only works if the graphical interface is available + as the visual properties are attributes of the view provider + (`obj.ViewObject`). + + Parameters + ---------- + target : App::DocumentObject + Any type of scripted object. + + This object will adopt the applicable visual properties, + `FontSize`, `TextColor`, `LineWidth`, `PointColor`, `LineColor`, + and `ShapeColor`, defined in the Draft toolbar + (`FreeCADGui.draftToolBar`) or will adopt + the properties from the `origin` object. + + The `target` is also placed in the construction group + if the construction mode in the Draft toolbar is active. + + origin : App::DocumentObject, optional + It defaults to `None`. + If it exists, it will provide the visual properties to assign + to `target`, with the exception of `BoundingBox`, `Proxy`, + `RootNode` and `Visibility`. + """ + if not target: + return + obrep = target.ViewObject + if not obrep: + return + ui = None + if FreeCAD.GuiUp: + if hasattr(FreeCADGui, "draftToolBar"): + ui = FreeCADGui.draftToolBar + if ui: + doc = FreeCAD.ActiveDocument + if ui.isConstructionMode(): + col = fcol = ui.getDefaultColor("constr") + gname = getParam("constructiongroupname", "Construction") + grp = doc.getObject(gname) + if not grp: + grp = doc.addObject("App::DocumentObjectGroup", gname) + grp.addObject(target) + if hasattr(obrep, "Transparency"): + obrep.Transparency = 80 + else: + col = ui.getDefaultColor("ui") + fcol = ui.getDefaultColor("face") + col = (float(col[0]), float(col[1]), float(col[2]), 0.0) + fcol = (float(fcol[0]), float(fcol[1]), float(fcol[2]), 0.0) + lw = ui.linewidth + fs = ui.fontsize + if not origin or not hasattr(origin, 'ViewObject'): + if "FontSize" in obrep.PropertiesList: + obrep.FontSize = fs + if "TextColor" in obrep.PropertiesList: + obrep.TextColor = col + if "LineWidth" in obrep.PropertiesList: + obrep.LineWidth = lw + if "PointColor" in obrep.PropertiesList: + obrep.PointColor = col + if "LineColor" in obrep.PropertiesList: + obrep.LineColor = col + if "ShapeColor" in obrep.PropertiesList: + obrep.ShapeColor = fcol + else: + matchrep = origin.ViewObject + for p in matchrep.PropertiesList: + if p not in ("DisplayMode", "BoundingBox", + "Proxy", "RootNode", "Visibility"): + if p in obrep.PropertiesList: + if not obrep.getEditorMode(p): + if hasattr(getattr(matchrep, p), "Value"): + val = getattr(matchrep, p).Value + else: + val = getattr(matchrep, p) + try: + setattr(obrep, p, val) + except Exception: + pass + if matchrep.DisplayMode in obrep.listDisplayModes(): + obrep.DisplayMode = matchrep.DisplayMode + if (hasattr(matchrep, "DiffuseColor") + and hasattr(obrep, "DiffuseColor")): + obrep.DiffuseColor = matchrep.DiffuseColor + + +formatObject = format_object + + +def get_selection(gui=FreeCAD.GuiUp): + """Return the current selected objects. + + This function only works if the graphical interface is available + as the selection module only works on the 3D view. + + It wraps around `FreeCADGui.Selection.getSelection` + + Parameters + ---------- + gui : bool, optional + It defaults to the value of `FreeCAD.GuiUp`, which is `True` + when the interface exists, and `False` otherwise. + + This value can be set to `False` to simulate + when the interface is not available. + + Returns + ------- + list of App::DocumentObject + Returns a list of objects in the current selection. + It can be an empty list if no object is selected. + + If the interface is not available, it returns `None`. + """ + if gui: + return FreeCADGui.Selection.getSelection() + return None + + +getSelection = get_selection + + +def get_selection_ex(gui=FreeCAD.GuiUp): + """Return the current selected objects together with their subelements. + + This function only works if the graphical interface is available + as the selection module only works on the 3D view. + + It wraps around `FreeCADGui.Selection.getSelectionEx` + + Parameters + ---------- + gui : bool, optional + It defaults to the value of `FreeCAD.GuiUp`, which is `True` + when the interface exists, and `False` otherwise. + + This value can be set to `False` to simulate + when the interface is not available. + + Returns + ------- + list of Gui::SelectionObject + Returns a list of `Gui::SelectionObject` in the current selection. + It can be an empty list if no object is selected. + + If the interface is not available, it returns `None`. + + Selection objects + ----------------- + One `Gui::SelectionObject` has attributes that indicate which specific + subelements, that is, vertices, wires, and faces, were selected. + This can be useful to operate on the subelements themselves. + If `G` is a `Gui::SelectionObject` + * `G.Object` is the selected object + * `G.ObjectName` is the name of the selected object + * `G.HasSubObjects` is `True` if there are subelements in the selection + * `G.SubObjects` is a tuple of the subelements' shapes + * `G.SubElementNames` is a tuple of the subelements' names + + `SubObjects` and `SubElementNames` should be empty tuples + if `HasSubObjects` is `False`. + """ + if gui: + return FreeCADGui.Selection.getSelectionEx() + return None + + +getSelectionEx = get_selection_ex + + +def select(objs=None, gui=FreeCAD.GuiUp): + """Unselects everything and selects only the given list of objects. + + This function only works if the graphical interface is available + as the selection module only works on the 3D view. + + Parameters + ---------- + objs : list of App::DocumentObject, optional + It defaults to `None`. + Any type of scripted object. + It may be a list of objects or a single object. + + gui : bool, optional + It defaults to the value of `FreeCAD.GuiUp`, which is `True` + when the interface exists, and `False` otherwise. + + This value can be set to `False` to simulate + when the interface is not available. + """ + if gui: + FreeCADGui.Selection.clearSelection() + if objs: + if not isinstance(objs, list): + objs = [objs] + for obj in objs: + if obj: + FreeCADGui.Selection.addSelection(obj) + + +def load_texture(filename, size=None, gui=FreeCAD.GuiUp): + """Return a Coin.SoSFImage to use as a texture for a 2D plane. + + This function only works if the graphical interface is available + as the visual properties that can be applied to a shape + are attributes of the view provider (`obj.ViewObject`). + + Parameters + ---------- + filename : str + A path to a pixel image file (PNG) that can be used as a texture + on the face of an object. + + size : tuple of two int, or a single int, optional + It defaults to `None`. + If a tuple is given, the two values define the width and height + in pixels to which the loaded image will be scaled. + If it is a single value, it is used for both dimensions. + + If it is `None`, the size will be determined from the `QImage` + created from `filename`. + + CURRENTLY the input `size` parameter IS NOT USED. + It always uses the `QImage` to determine this information. + + gui : bool, optional + It defaults to the value of `FreeCAD.GuiUp`, which is `True` + when the interface exists, and `False` otherwise. + + This value can be set to `False` to simulate + when the interface is not available. + + Returns + ------- + coin.SoSFImage + An image object with the appropriate size, number of components + (grayscale, grayscale and transparency, color, + color and transparency), and byte data. + + It returns `None` if the interface is not available, + or if there is a problem creating the image. + """ + if gui: + # from pivy import coin + # from PySide import QtGui, QtSvg + try: + p = QtGui.QImage(filename) + + if p.isNull(): + _wrn("load_texture: " + _tr("image is Null")) + + if not os.path.exists(filename): + raise FileNotFoundError(-1, + _tr("filename does not exist " + "on the system or " + "on the resource file"), + filename) + + # This is buggy so it was de-activated. + # + # TODO: allow SVGs to use resolutions + # if size and (".svg" in filename.lower()): + # # this is a pattern, not a texture + # if isinstance(size, int): + # size = (size, size) + # svgr = QtSvg.QSvgRenderer(filename) + # p = QtGui.QImage(size[0], size[1], + # QtGui.QImage.Format_ARGB32) + # pa = QtGui.QPainter() + # pa.begin(p) + # svgr.render(pa) + # pa.end() + # else: + # p = QtGui.QImage(filename) + size = coin.SbVec2s(p.width(), p.height()) + buffersize = p.byteCount() + width = size[0] + height = size[1] + numcomponents = int(float(buffersize) / (width * height)) + + img = coin.SoSFImage() + byteList = [] + # isPy2 = sys.version_info.major < 3 + isPy2 = six.PY2 + + # The SoSFImage needs to be filled with bytes. + # The pixel information is converted into a Qt color, gray, + # red, green, blue, or transparency (alpha), + # depending on the input image. + # + # If Python 2 is used, the color is turned into a character, + # which is of type 'byte', and added to the byte list. + # If Python 3 is used, characters are unicode strings, + # so they need to be encoded into 'latin-1' + # to produce the correct bytes for the list. + for y in range(height): + # line = width*numcomponents*(height-(y)); + for x in range(width): + rgb = p.pixel(x, y) + if numcomponents == 1 or numcomponents == 2: + gray = chr(QtGui.qGray(rgb)) + if isPy2: + byteList.append(gray) + else: + byteList.append(gray.encode('latin-1')) + + if numcomponents == 2: + alpha = chr(QtGui.qAlpha(rgb)) + if isPy2: + byteList.append(alpha) + else: + byteList.append(alpha.encode('latin-1')) + elif numcomponents == 3 or numcomponents == 4: + red = chr(QtGui.qRed(rgb)) + green = chr(QtGui.qGreen(rgb)) + blue = chr(QtGui.qBlue(rgb)) + + if isPy2: + byteList.append(red) + byteList.append(green) + byteList.append(blue) + else: + byteList.append(red.encode('latin-1')) + byteList.append(green.encode('latin-1')) + byteList.append(blue.encode('latin-1')) + + if numcomponents == 4: + alpha = chr(QtGui.qAlpha(rgb)) + if isPy2: + byteList.append(alpha) + else: + byteList.append(alpha.encode('latin-1')) + # line += numcomponents + + _bytes = b"".join(byteList) + img.setValue(size, numcomponents, _bytes) + except FileNotFoundError as exc: + _wrn("load_texture: {0}, {1}".format(exc.strerror, + exc.filename)) + return None + except Exception as exc: + _wrn(str(exc)) + _wrn("load_texture: " + _tr("unable to load texture")) + return None + else: + return img + return None + + +loadTexture = load_texture From feaf9b24dcb2eaa996dd226452f87e8078d03e11 Mon Sep 17 00:00:00 2001 From: hlg Date: Thu, 9 Jan 2020 19:46:54 +0100 Subject: [PATCH 12/14] [Arch] fix true north handling on IFC import and export --- src/Mod/Arch/exportIFC.py | 4 ++-- src/Mod/Arch/importIFC.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index cfeee7e9f2..c5f2717310 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -224,8 +224,8 @@ def export(exportList,filename,colors=None,preferences=None): objectslist = [obj for obj in objectslist if obj != contextCreator.project_object] if Draft.getObjectsOfType(objectslist, "Site"): # we assume one site and one representation context only - trueNorthX = math.tan(-Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian)) - contextCreator.model_context.TrueNorth.DirectionRatios = (trueNorthX, 1.) + 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)) products = {} # { Name: IfcEntity, ... } subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 8295110fe5..23fc52ae45 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -836,9 +836,10 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None): obj.PostalCode = product.SiteAddress.PostalCode project = product.Decomposes[0].RelatingObject modelRC = next((rc for rc in project.RepresentationContexts if rc.ContextType == "Model"), None) - if modelRC and modelRC.TrueNorth and modelRC.TrueNorth.DirectionRatios[1] > 0: - obj.Declination = -math.degrees(math.atan(modelRC.TrueNorth.DirectionRatios[0] / modelRC.TrueNorth.DirectionRatios[1])) - if(FreeCAD.GuiUp): + if modelRC and modelRC.TrueNorth: + (x, y) = modelRC.TrueNorth.DirectionRatios[:2] + obj.Declination = ((math.degrees(math.atan2(y,x))-90+180)%360)-180 + if (FreeCAD.GuiUp): obj.ViewObject.CompassRotation.Value = obj.Declination try: From 885046b1c6290c8f71ea202d64ec0e7a37e7ee46 Mon Sep 17 00:00:00 2001 From: paullee0 Date: Fri, 10 Jan 2020 04:16:53 +0800 Subject: [PATCH 13/14] [DraftGeomUtils] OffsetWire() to accept input as Face as previously did. Forum Discussion - https://forum.freecadweb.org/viewtopic.php?f=23&t=42282&p=359233#p359233 --- src/Mod/Draft/DraftGeomUtils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index dbef8c3ba2..67865dd3a8 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -1218,8 +1218,9 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a 'dvec' vector to offset is now derived (and can be ignored) in this function if widthList and alignList are provided - 'dvec' to be obsolete in future ? ''' - # Accept 'wire' as a list of edges, use the list directly, or previously a wire - if isinstance(wire,Part.Wire): + # Accept 'wire' as a list of edges (use the list directly), or previously as a wire or a face (Draft Wire with MakeFace True or False supported) + + if isinstance(wire,Part.Wire) or isinstance(wire,Part.Face): edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) elif isinstance(wire, list): if isinstance(wire[0],Part.Edge): @@ -1273,7 +1274,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a firstDir = 1 firstAlign = 'Center' except: - print ("alignListC[0] has no value ") # Should no longer happen for ArchWall - as aligns are 'filled in' by ArchWall + pass # Should no longer happen for ArchWall - as aligns are 'filled in' by ArchWall # If not provided by alignListC checked above, check the direction of offset in dvec (not 'align') From 4b5f91f4c3141f41195f03e05792553a86cf53b8 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 9 Jan 2020 17:01:28 -0600 Subject: [PATCH 14/14] Draft: test_offset, test wire and closed shape (rectangle) --- src/Mod/Draft/drafttests/test_modification.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/test_modification.py b/src/Mod/Draft/drafttests/test_modification.py index ca6d0fc4df..75e51b3ee2 100644 --- a/src/Mod/Draft/drafttests/test_modification.py +++ b/src/Mod/Draft/drafttests/test_modification.py @@ -106,7 +106,26 @@ class DraftModification(unittest.TestCase): self.assertTrue(obj.Start.isEqual(c, 1e-12), "'{}' failed".format(operation)) - def test_offset(self): + def test_offset_open(self): + """Create a wire, then produce an offset copy.""" + operation = "Draft Offset" + _msg(" Test '{}'".format(operation)) + a = Vector(0, 2, 0) + b = Vector(2, 4, 0) + c = Vector(5, 2, 0) + _msg(" Wire") + _msg(" a={0}, b={1}".format(a, b)) + _msg(" c={0}".format(c)) + wire = Draft.makeWire([a, b, c]) + App.ActiveDocument.recompute() + + offset = Vector(-1, 1, 0) + _msg(" Offset") + _msg(" vector={}".format(offset)) + obj = Draft.offset(wire, offset, copy=True) + self.assertTrue(obj, "'{}' failed".format(operation)) + + def test_offset_closed(self): """Create a rectangle, then produce an offset copy.""" operation = "Draft Offset" _msg(" Test '{}'".format(operation))