[TD]Cosmetic function overhaul (#14216)

* [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
This commit is contained in:
WandererFan
2024-05-23 09:41:42 -04:00
committed by GitHub
parent d12635246f
commit b71c2a3278
56 changed files with 1812 additions and 1076 deletions

View File

@@ -20,19 +20,29 @@
* *
***************************************************************************/
//! 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
@@ -66,26 +76,26 @@ 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");
// EndType.setEnums(ArrowTypeEnums);
// ADD_PROPERTY(EndType, (prefEnd()));
StartSymbol.setEnums(ArrowPropEnum::ArrowTypeEnums);
ADD_PROPERTY(StartSymbol, (0l)); //filled arrow
ADD_PROPERTY(StartSymbol, (FilledArrow)); //filled arrow
// ADD_PROPERTY_TYPE(StartSymbol, (0), group, App::Prop_None, "Symbol (arrowhead) for start of line");
EndSymbol.setEnums(ArrowPropEnum::ArrowTypeEnums);
ADD_PROPERTY(EndSymbol, (7l)); //no symbol
// ADD_PROPERTY_TYPE(EndSymbol, (0), group, App::Prop_None, "Symbol (arrowhead) for end of line");
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);
@@ -108,12 +118,16 @@ void DrawLeaderLine::onChanged(const App::Property* prop)
short DrawLeaderLine::mustExecute() const
{
if (!isRestoring() && LeaderParent.isTouched()) {
return true; // Property changed
return 1; // Property changed
}
const App::DocumentObject* docObj = getBaseObject();
if (docObj && docObj->isTouched()) {
return true; // Object property points to is touched
return 1; // Object property points to is touched
}
if (WayPoints.isTouched()) {
return 1;
}
return DrawView::mustExecute();
@@ -121,11 +135,13 @@ short DrawLeaderLine::mustExecute() const
App::DocumentObjectExecReturn *DrawLeaderLine::execute()
{
// Base::Console().Message("DLL::execute()\n");
// Base::Console().Message("DLL::execute()\n");
if (!keepUpdated()) {
return App::DocumentObject::StdReturn;
}
adjustLastSegment();
// is horizLastSegment something that should be done only at draw time?
horizLastSegment();
overrideKeepUpdated(false);
return DrawView::execute();
}
@@ -137,7 +153,7 @@ DrawView* DrawLeaderLine::getBaseView() const
return nullptr;
}
DrawView* cast = dynamic_cast<DrawView*>(baseObj);
auto cast = dynamic_cast<DrawView*>(baseObj);
return cast;
}
@@ -155,8 +171,6 @@ bool DrawLeaderLine::keepUpdated()
return view->keepUpdated();
}
//need separate getParentScale()???
double DrawLeaderLine::getBaseScale() const
{
// Base::Console().Message("DLL::getBaseScale()\n");
@@ -174,11 +188,7 @@ double DrawLeaderLine::getScale() const
return 1.0;
}
DrawView* parent = getBaseView();
if (!parent) {
return 1.0;
}
return parent->getScale();
return getBaseScale();
}
Base::Vector3d DrawLeaderLine::getAttachPoint()
@@ -190,23 +200,37 @@ Base::Vector3d DrawLeaderLine::getAttachPoint()
);
}
void DrawLeaderLine::adjustLastSegment()
//! 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::adjustLastSegment()\n");
bool adjust = AutoHorizontal.getValue();
std::vector<Base::Vector3d> wp = WayPoints.getValues();
if (adjust && wp.size() > 1) {
int iLast = wp.size() - 1;
int iPen = wp.size() - 2;
// 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;
}
WayPoints.setValues(wp);
return wp;
}
//middle of last line segment
//! 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();
@@ -215,17 +239,17 @@ Base::Vector3d DrawLeaderLine::getTileOrigin() const
Base::Vector3d second = wp.rbegin()[1];
return (last + second) / 2.0;
}
Base::Console().Warning("DLL::getTileOrigin - no waypoints\n");
return Base::Vector3d();
}
//start of last line segment
//! 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
return wp.rbegin()[1]; // second point from end
}
Base::Console().Warning("DLL::getKinkPoint - no waypoints\n");
@@ -239,12 +263,180 @@ Base::Vector3d DrawLeaderLine::getTailPoint() const
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);