* [TD]Cosmetic geometry and tools update - all cosmetics to store geometry in same form - all cosmetics to survive scaling and rotation - extension functions to survive scaling and rotation * [TD]overhaul leader point storage and editing - add py routine makeLeader(points) * [TD]add leader conversion utility * [TD]Set Leader RotateWithView default to true * [TD]fix intersection vertex position * [TD]add CosmeticEdge::makeLineFromCanonicalPoints * [TD]fix 2 Extension tools - positioning in DrawCosmeticCircle - mishandling of points in execLineParallelPerpendicular * [TD]Remove duplicate constexpr * [TD]fix 2x Cosmetic arc tools * [TD]refactor LineFormat out of Cosmetic * [TD]move cosmetic appearance settings to LineFormat * [TD]remove 2 unused methods * [TD]apply format to blue line & circle tools * [TD]fix ballon arrowhead does not rotate with view * [TD]fix CosmeticCircle3Points * [TD]allow multiple cosmetic object deletions * [TD]fix extend/shorten centerline
469 lines
17 KiB
C++
469 lines
17 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2019 WandererFan <wandererfan@gmail.com> *
|
|
* *
|
|
* 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
//! DrawLeaderLine - a class for storing leader line attributes and providing methods to apply transformations on leader geometry.
|
|
//! Waypoints are to be stored as displacements from the first Waypoint in printed page coordinates (mm, conventional
|
|
//! X and Y axes, (0, 0) at lower left). The first Waypoint is set to (0,0) after the displacements are calculated.
|
|
//! The leader's X,Y position is relative to the parent's origin. The X,Y position is unrotated and unscaled.
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#include <App/Document.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Parameter.h>
|
|
|
|
#include "DrawViewPart.h"
|
|
#include "DrawPage.h"
|
|
#include "DrawLeaderLine.h"
|
|
#include "DrawLeaderLinePy.h" // generated from DrawLeaderLinePy.xml
|
|
#include "ArrowPropEnum.h"
|
|
#include "DrawView.h"
|
|
#include "Preferences.h"
|
|
#include "DrawUtil.h"
|
|
|
|
|
|
using namespace TechDraw;
|
|
using DU = DrawUtil;
|
|
|
|
//===========================================================================
|
|
// DrawLeaderLine - Base class for drawing leader based features
|
|
//===========================================================================
|
|
|
|
PROPERTY_SOURCE(TechDraw::DrawLeaderLine, TechDraw::DrawView)
|
|
|
|
//TODO: share this between DrawViewBalloon, DrawLeaderLine, QGIArrow, Prefs, etc
|
|
//const char* DrawLeaderLine::ArrowTypeEnums[]= {
|
|
// "FILLED_ARROW",
|
|
// "OPEN_ARROW",
|
|
// "TICK",
|
|
// "DOT",
|
|
// "OPEN_CIRCLE",
|
|
// "FORK",
|
|
// "FILLED_TRIANGLE",
|
|
// "NONE"
|
|
// NULL};
|
|
//const char* DrawLeaderLine::ArrowTypeEnums2[]= {
|
|
// "FILLED_ARROW",
|
|
// "OPEN_ARROW",
|
|
// "TICK",
|
|
// "DOT",
|
|
// "OPEN_CIRCLE",
|
|
// "FORK",
|
|
// "FILLED_TRIANGLE",
|
|
// "NONE"
|
|
// NULL};
|
|
|
|
DrawLeaderLine::DrawLeaderLine()
|
|
{
|
|
static const char *group = "Leader";
|
|
|
|
constexpr long int FilledArrow{0l};
|
|
constexpr long int NoEnd{7l};
|
|
|
|
ADD_PROPERTY_TYPE(LeaderParent, (nullptr), group, (App::PropertyType)(App::Prop_None),
|
|
"View to which this leader is attached");
|
|
LeaderParent.setScope(App::LinkScope::Global);
|
|
ADD_PROPERTY_TYPE(WayPoints, (Base::Vector3d()) ,group, App::Prop_None,
|
|
"Intermediate points for Leader line");
|
|
|
|
StartSymbol.setEnums(ArrowPropEnum::ArrowTypeEnums);
|
|
ADD_PROPERTY(StartSymbol, (FilledArrow)); //filled arrow
|
|
|
|
EndSymbol.setEnums(ArrowPropEnum::ArrowTypeEnums);
|
|
ADD_PROPERTY(EndSymbol, (NoEnd)); //no symbol
|
|
|
|
ADD_PROPERTY_TYPE(Scalable ,(false), group, App::Prop_None, "Scale line with LeaderParent");
|
|
ADD_PROPERTY_TYPE(AutoHorizontal ,(getDefAuto()), group, App::Prop_None, "Forces last line segment to be horizontal");
|
|
ADD_PROPERTY_TYPE(RotatesWithParent ,(true), group, App::Prop_None,
|
|
"If true, leader rotates around parent. If false, only first segment of leader changes with parent rotation.");
|
|
|
|
|
|
//hide the DrawView properties that don't apply to Leader
|
|
ScaleType.setStatus(App::Property::ReadOnly, true);
|
|
ScaleType.setStatus(App::Property::Hidden, true);
|
|
Scale.setStatus(App::Property::ReadOnly, true);
|
|
Scale.setStatus(App::Property::Hidden, true);
|
|
Rotation.setStatus(App::Property::ReadOnly, true);
|
|
Rotation.setStatus(App::Property::Hidden, true);
|
|
Caption.setStatus(App::Property::Hidden, true);
|
|
|
|
LockPosition.setValue(true);
|
|
LockPosition.setStatus(App::Property::Hidden, true);
|
|
}
|
|
|
|
void DrawLeaderLine::onChanged(const App::Property* prop)
|
|
{
|
|
DrawView::onChanged(prop);
|
|
}
|
|
|
|
short DrawLeaderLine::mustExecute() const
|
|
{
|
|
if (!isRestoring() && LeaderParent.isTouched()) {
|
|
return 1; // Property changed
|
|
}
|
|
|
|
const App::DocumentObject* docObj = getBaseObject();
|
|
if (docObj && docObj->isTouched()) {
|
|
return 1; // Object property points to is touched
|
|
}
|
|
|
|
if (WayPoints.isTouched()) {
|
|
return 1;
|
|
}
|
|
|
|
return DrawView::mustExecute();
|
|
}
|
|
|
|
App::DocumentObjectExecReturn *DrawLeaderLine::execute()
|
|
{
|
|
// Base::Console().Message("DLL::execute()\n");
|
|
if (!keepUpdated()) {
|
|
return App::DocumentObject::StdReturn;
|
|
}
|
|
|
|
// is horizLastSegment something that should be done only at draw time?
|
|
horizLastSegment();
|
|
overrideKeepUpdated(false);
|
|
return DrawView::execute();
|
|
}
|
|
|
|
DrawView* DrawLeaderLine::getBaseView() const
|
|
{
|
|
App::DocumentObject* baseObj = LeaderParent.getValue();
|
|
if (!baseObj) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto cast = dynamic_cast<DrawView*>(baseObj);
|
|
return cast;
|
|
}
|
|
|
|
App::DocumentObject* DrawLeaderLine::getBaseObject() const
|
|
{
|
|
return getBaseView();
|
|
}
|
|
|
|
bool DrawLeaderLine::keepUpdated()
|
|
{
|
|
DrawView* view = getBaseView();
|
|
if (!view) {
|
|
return false;
|
|
}
|
|
return view->keepUpdated();
|
|
}
|
|
|
|
double DrawLeaderLine::getBaseScale() const
|
|
{
|
|
// Base::Console().Message("DLL::getBaseScale()\n");
|
|
DrawView* parent = getBaseView();
|
|
if (!parent) {
|
|
return 1.0;
|
|
}
|
|
return parent->getScale();
|
|
}
|
|
|
|
double DrawLeaderLine::getScale() const
|
|
{
|
|
// Base::Console().Message("DLL::getScale()\n");
|
|
if (!Scalable.getValue()) {
|
|
return 1.0;
|
|
}
|
|
|
|
return getBaseScale();
|
|
}
|
|
|
|
Base::Vector3d DrawLeaderLine::getAttachPoint()
|
|
{
|
|
return Base::Vector3d(
|
|
X.getValue(),
|
|
Y.getValue(),
|
|
0.0
|
|
);
|
|
}
|
|
|
|
//! unit agnostic conversion of last segment to horizontal. need to do this at drawing time otherwise
|
|
//! we just realign the canonical form.
|
|
void DrawLeaderLine::horizLastSegment()
|
|
{
|
|
// Base::Console().Message("DLL::horizLastSegment() - auto: %d\n", AutoHorizontal.getValue());
|
|
bool adjust = AutoHorizontal.getValue();
|
|
if (!adjust) {
|
|
return;
|
|
}
|
|
|
|
auto temp = horizLastSegment(WayPoints.getValues());
|
|
WayPoints.setValues(temp);
|
|
}
|
|
|
|
std::vector<Base::Vector3d> DrawLeaderLine::horizLastSegment(const std::vector<Base::Vector3d>& inDeltas)
|
|
{
|
|
// Base::Console().Message("DLL::horizLastSegment(in: %d)\n", inDeltas.size());
|
|
|
|
std::vector<Base::Vector3d> wp = inDeltas;
|
|
if (wp.size() > 1) {
|
|
size_t iLast = wp.size() - 1;
|
|
size_t iPen = wp.size() - 2;
|
|
Base::Vector3d last = wp.at(iLast);
|
|
Base::Vector3d penUlt = wp.at(iPen);
|
|
last.y = penUlt.y;
|
|
wp.at(iLast) = last;
|
|
}
|
|
return wp;
|
|
}
|
|
|
|
//! returns the mid point of last segment. used by leader decorators like weld symbol.
|
|
Base::Vector3d DrawLeaderLine::getTileOrigin() const
|
|
{
|
|
std::vector<Base::Vector3d> wp = WayPoints.getValues();
|
|
if (wp.size() > 1) {
|
|
Base::Vector3d last = wp.rbegin()[0];
|
|
Base::Vector3d second = wp.rbegin()[1];
|
|
return (last + second) / 2.0;
|
|
}
|
|
|
|
Base::Console().Warning("DLL::getTileOrigin - no waypoints\n");
|
|
return Base::Vector3d();
|
|
}
|
|
|
|
//! returns start of last line segment
|
|
Base::Vector3d DrawLeaderLine::getKinkPoint() const
|
|
{
|
|
std::vector<Base::Vector3d> wp = WayPoints.getValues();
|
|
if (wp.size() > 1) {
|
|
return wp.rbegin()[1]; // second point from end
|
|
}
|
|
|
|
Base::Console().Warning("DLL::getKinkPoint - no waypoints\n");
|
|
return Base::Vector3d();
|
|
}
|
|
|
|
//end of last line segment
|
|
Base::Vector3d DrawLeaderLine::getTailPoint() const
|
|
{
|
|
std::vector<Base::Vector3d> wp = WayPoints.getValues();
|
|
if (!wp.empty()) {
|
|
return wp.rbegin()[0]; // Last
|
|
}
|
|
|
|
Base::Console().Warning("DLL::getTailPoint - no waypoints\n");
|
|
return Base::Vector3d();
|
|
}
|
|
|
|
|
|
//! create a new leader feature from parameters. Used by python method makeLeader.
|
|
//! pagePoints are in mm from bottom left of page.
|
|
DrawLeaderLine* DrawLeaderLine::makeLeader(DrawViewPart* parent, std::vector<Base::Vector3d> pagePoints, int iStartSymbol, int iEndSymbol)
|
|
{
|
|
// Base::Console().Message("DLL::makeLeader(%s, %d, %d, %d)\n", parent->getNameInDocument(), pagePoints.size(), iStartSymbol, iEndSymbol);
|
|
if (pagePoints.size() < 2) {
|
|
Base::Console().Message("DLL::makeLeader - not enough pagePoints\n");
|
|
return {};
|
|
}
|
|
|
|
// this is +/- the same code as in TaskLeaderLine::createLeaderFeature()
|
|
const std::string objectName{"LeaderLine"};
|
|
const std::string leaderType = "TechDraw::DrawLeaderLine";
|
|
std::string leaderName = parent->getDocument()->getUniqueObjectName(objectName.c_str());
|
|
std::string pageName = parent->findParentPage()->getNameInDocument();
|
|
std::string parentName = parent->getNameInDocument();
|
|
|
|
Base::Interpreter().runStringArg("App.activeDocument().addObject('%s', '%s')",
|
|
leaderType.c_str(), leaderName.c_str());
|
|
Base::Interpreter().runStringArg("App.activeDocument().%s.translateLabel('DrawLeaderLine', 'LeaderLine', '%s')",
|
|
leaderName.c_str(), leaderName.c_str());
|
|
Base::Interpreter().runStringArg("App.activeDocument().%s.addView(App.activeDocument().%s)",
|
|
pageName.c_str(), leaderName.c_str());
|
|
Base::Interpreter().runStringArg("App.activeDocument().%s.LeaderParent = App.activeDocument().%s",
|
|
leaderName.c_str(), parentName.c_str());
|
|
// we assume here that the caller will handle AutoHorizontal, Scalable and Rotatable as required
|
|
|
|
|
|
App::DocumentObject* obj = parent->getDocument()->getObject(leaderName.c_str());
|
|
if (!obj || !obj->isDerivedFrom(TechDraw::DrawLeaderLine::getClassTypeId())) {
|
|
throw Base::RuntimeError("DrawLeaderLine::makeLeader - new object not found");
|
|
}
|
|
|
|
// set leader x,y position
|
|
auto leaderFeature = static_cast<TechDraw::DrawLeaderLine*>(obj);
|
|
Base::Vector3d parentPagePos{ parent->X.getValue(), parent->Y.getValue(), 0.0};
|
|
Base::Vector3d leaderPagePos = pagePoints.front() - parentPagePos;
|
|
bool force = true; // update position even though leaders default to locked.
|
|
leaderFeature->setPosition(leaderPagePos.x, leaderPagePos.y, force);
|
|
|
|
// page positions to deltas
|
|
std::vector<Base::Vector3d> pageDeltas;
|
|
for (auto& point : pagePoints) {
|
|
auto temp = point - pagePoints.front();
|
|
pageDeltas.emplace_back(temp);
|
|
}
|
|
|
|
// deltas to unscaled, unrotated form
|
|
auto leaderPoints = leaderFeature->makeCanonicalPoints(pageDeltas);
|
|
// invert the canonical points
|
|
std::vector<Base::Vector3d> inverted;
|
|
inverted.reserve(leaderPoints.size());
|
|
for (auto& point : leaderPoints) {
|
|
inverted.push_back(DU::invertY(point));
|
|
}
|
|
leaderFeature->WayPoints.setValues(inverted);
|
|
|
|
leaderFeature->StartSymbol.setValue(iStartSymbol);
|
|
leaderFeature->EndSymbol.setValue(iEndSymbol);
|
|
|
|
parent->touch();
|
|
|
|
return leaderFeature;
|
|
}
|
|
|
|
//! return scaled and rotated copies of the WayPoints (page position deltas from 1st point)
|
|
//! used by QGILL.
|
|
std::vector<Base::Vector3d> DrawLeaderLine::getScaledAndRotatedPoints(bool doScale, bool doRotate) const
|
|
{
|
|
// Base::Console().Message("DLL::getScaledAndRotatedPoints(%d, %d)\n", doScale, doRotate);
|
|
auto dvp = getBaseView();
|
|
if (!dvp) {
|
|
// document is restoring?
|
|
// Base::Console().Message("DLL::getScaledAndRotatedPoints - no DV\n");
|
|
return {};
|
|
}
|
|
|
|
double scale{1.0};
|
|
if (Scalable.getValue() && doScale) {
|
|
scale = dvp->getScale();
|
|
}
|
|
|
|
double rotationRad{0.0};
|
|
if (doRotate) {
|
|
rotationRad = dvp->Rotation.getValue() * M_PI / DegreesHalfCircle;
|
|
}
|
|
|
|
std::vector<Base::Vector3d> pointsAll = WayPoints.getValues();
|
|
std::vector<Base::Vector3d> result;
|
|
for (auto& point : pointsAll) {
|
|
Base::Vector3d newPoint = DU::invertY(point * scale);
|
|
if (rotationRad != 0.0) {
|
|
// the waypoints use conventional coords
|
|
newPoint.RotateZ(rotationRad);
|
|
}
|
|
result.push_back(DU::invertY(newPoint));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//! return unscaled and unrotated versions of the input points. input is expected to in mm (Rez::appX()),
|
|
//! and conventional Y axis (+ up)
|
|
//! used by QGILL.
|
|
std::vector<Base::Vector3d>
|
|
DrawLeaderLine::makeCanonicalPoints(const std::vector<Base::Vector3d>& inPoints,
|
|
bool doScale,
|
|
bool doRotate) const
|
|
{
|
|
// Base::Console().Message("DLL::makeCanonicalPoints(%d, %d, %d)\n", inPoints.size(), doScale, doRotate);
|
|
auto dvp = getBaseView();
|
|
|
|
double scale{1.0};
|
|
if (Scalable.getValue() && doScale) {
|
|
scale = dvp->getScale();
|
|
}
|
|
|
|
double rotationRad{0.0};
|
|
if (doRotate) {
|
|
rotationRad = - dvp->Rotation.getValue() * M_PI / DegreesHalfCircle;
|
|
}
|
|
|
|
std::vector<Base::Vector3d> result;
|
|
for (auto& point : inPoints) {
|
|
Base::Vector3d newPoint = point / scale;
|
|
if (rotationRad != 0.0) {
|
|
newPoint.RotateZ(rotationRad);
|
|
}
|
|
result.push_back(newPoint);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//! as makeCanonicalPoints, but accepts and returns inverted points
|
|
std::vector<Base::Vector3d>
|
|
DrawLeaderLine::makeCanonicalPointsInverted(const std::vector<Base::Vector3d>& inPoints,
|
|
bool doScale,
|
|
bool doRotate) const
|
|
{
|
|
std::vector<Base::Vector3d> conventionalPoints;
|
|
conventionalPoints.reserve(inPoints.size());
|
|
for (auto& point : inPoints) {
|
|
conventionalPoints.push_back(DU::invertY(point));
|
|
}
|
|
auto conventionalCanon = makeCanonicalPoints(inPoints, doScale, doRotate);
|
|
std::vector<Base::Vector3d> invertedPoints;
|
|
invertedPoints.reserve(inPoints.size());
|
|
for (auto& point : conventionalCanon) {
|
|
invertedPoints.push_back(DU::invertY(point));
|
|
}
|
|
return invertedPoints;
|
|
}
|
|
|
|
//! returns true if parent exists. if parent is a DVP it must have geometry.
|
|
bool DrawLeaderLine::isParentReady() const
|
|
{
|
|
TechDraw::DrawView* parent = getBaseView();
|
|
auto dvp = dynamic_cast<TechDraw::DrawViewPart*>(parent);
|
|
if (!parent || (dvp && !dvp->hasGeometry())) {
|
|
// still restoring or
|
|
// we are attached to a dvp that has no geometry, so don't bother trying to draw yet
|
|
Base::Console().Message("DLL:: - no parent or geometry\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DrawLeaderLine::getDefAuto() const
|
|
{
|
|
return Preferences::getPreferenceGroup("LeaderLine")->GetBool("AutoHorizontal", true);
|
|
}
|
|
|
|
|
|
PyObject *DrawLeaderLine::getPyObject()
|
|
{
|
|
if (PythonObject.is(Py::_None())) {
|
|
// ref counter is set to 1
|
|
PythonObject = Py::Object(new DrawLeaderLinePy(this), true);
|
|
}
|
|
return Py::new_reference_to(PythonObject);
|
|
}
|
|
|
|
// Python Drawing feature ---------------------------------------------------------
|
|
|
|
namespace App {
|
|
/// @cond DOXERR
|
|
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawLeaderLinePython, TechDraw::DrawLeaderLine)
|
|
template<> const char* TechDraw::DrawLeaderLinePython::getViewProviderName() const {
|
|
return "TechDrawGui::ViewProviderLeader";
|
|
}
|
|
/// @endcond
|
|
|
|
// explicit template instantiation
|
|
template class TechDrawExport FeaturePythonT<TechDraw::DrawLeaderLine>;
|
|
}
|
|
|