Every basic data type is stored in Base module, color is standing out as one that does not. Moving it to Base opens possibilities to integrate it better with the rest of FreeCAD.
2024 lines
68 KiB
C++
2024 lines
68 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2015 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
#include <boost_regex.hpp>
|
|
|
|
#include <QChar>
|
|
#include <QPointF>
|
|
#include <QString>
|
|
|
|
#include <BRepAdaptor_Curve.hxx>
|
|
#include <BRepAdaptor_Surface.hxx>
|
|
#include <BRepBndLib.hxx>
|
|
#include <BRepBuilderAPI_MakeEdge.hxx>
|
|
#include <BRepExtrema_DistShapeShape.hxx>
|
|
#include <BRepLProp_CLProps.hxx>
|
|
#include <BRepLProp_CurveTool.hxx>
|
|
#include <BRepLProp_SLProps.hxx>
|
|
#include <BRepTools.hxx>
|
|
#include <BRep_Builder.hxx>
|
|
#include <BRep_Tool.hxx>
|
|
#include <GCPnts_AbscissaPoint.hxx>
|
|
#include <GeomAPI_ExtremaCurveCurve.hxx>
|
|
#include <Precision.hxx>
|
|
#include <TopExp.hxx>
|
|
#include <TopExp_Explorer.hxx>
|
|
#include <TopTools_IndexedMapOfShape.hxx>
|
|
#include <gp_Ax3.hxx>
|
|
#include <gp_Dir.hxx>
|
|
#include <gp_Elips.hxx>
|
|
#include <gp_Pnt.hxx>
|
|
#include <gp_Vec.hxx>
|
|
#endif
|
|
|
|
#include <Base/Console.h>
|
|
#include <Base/FileInfo.h>
|
|
#include <Base/Parameter.h>
|
|
#include <Base/Stream.h>
|
|
#include <Base/UnitsApi.h>
|
|
#include <Base/Vector3D.h>
|
|
|
|
#include "DrawUtil.h"
|
|
#include "GeometryObject.h"
|
|
#include "LineGroup.h"
|
|
#include "Preferences.h"
|
|
#include "DrawViewPart.h"
|
|
|
|
|
|
using namespace TechDraw;
|
|
|
|
/*static*/ int DrawUtil::getIndexFromName(const std::string& geomName)
|
|
{
|
|
// Base::Console().Message("DU::getIndexFromName(%s)\n", geomName.c_str());
|
|
boost::regex re("\\d+$");// one of more digits at end of string
|
|
boost::match_results<std::string::const_iterator> what;
|
|
boost::match_flag_type flags = boost::match_default;
|
|
// char* endChar;
|
|
std::string::const_iterator begin = geomName.begin();
|
|
auto pos = geomName.rfind('.');
|
|
if (pos != std::string::npos) {
|
|
begin += pos + 1;
|
|
}
|
|
std::string::const_iterator end = geomName.end();
|
|
std::stringstream ErrorMsg;
|
|
|
|
if (geomName.empty()) {
|
|
throw Base::ValueError("getIndexFromName - empty geometry name");
|
|
}
|
|
|
|
|
|
if (boost::regex_search(begin, end, what, re, flags)) {
|
|
return int(std::stoi(what.str()));
|
|
} else {
|
|
ErrorMsg << "getIndexFromName: malformed geometry name - " << geomName;
|
|
throw Base::ValueError(ErrorMsg.str());
|
|
}
|
|
}
|
|
|
|
/*static*/ std::vector<int> DrawUtil::getIndexFromName(const std::vector<std::string>& geomNames)
|
|
{
|
|
std::vector<int> result;
|
|
result.reserve(200);
|
|
for (const std::string& geomName : geomNames) {
|
|
result.push_back(getIndexFromName(geomName));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string DrawUtil::getGeomTypeFromName(const std::string& geomName)
|
|
{
|
|
if (geomName.empty()) {
|
|
throw Base::ValueError("getGeomTypeFromName - empty geometry name");
|
|
}
|
|
|
|
boost::regex re("^[a-zA-Z]*");//one or more letters at start of string
|
|
boost::match_results<std::string::const_iterator> what;
|
|
boost::match_flag_type flags = boost::match_default;
|
|
std::string::const_iterator begin = geomName.begin();
|
|
auto pos = geomName.rfind('.');
|
|
if (pos != std::string::npos) {
|
|
begin += pos + 1;
|
|
}
|
|
std::string::const_iterator end = geomName.end();
|
|
std::stringstream ErrorMsg;
|
|
|
|
if (boost::regex_search(begin, end, what, re, flags)) {
|
|
return what.str();
|
|
} else {
|
|
ErrorMsg << "In getGeomTypeFromName: malformed geometry name - " << geomName;
|
|
throw Base::ValueError(ErrorMsg.str());
|
|
}
|
|
}
|
|
|
|
//! Check if all geomNames are of same geomType
|
|
//! Edge1, Edge2, Edge3 -> true
|
|
//! Edge1, Edge2, Vertex7 -> false
|
|
bool DrawUtil::isGeomTypeConsistent(const std::vector<std::string>& geomNames)
|
|
{
|
|
std::string reference = getGeomTypeFromName(geomNames.at(0));
|
|
for (std::string geomName : geomNames) {
|
|
if (reference != getGeomTypeFromName(geomName)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string DrawUtil::makeGeomName(const std::string& geomType, int index)
|
|
{
|
|
std::stringstream newName;
|
|
newName << geomType << index;
|
|
return newName.str();
|
|
}
|
|
|
|
//! true if v1 and v2 are the same geometric point within tolerance
|
|
bool DrawUtil::isSamePoint(TopoDS_Vertex v1, TopoDS_Vertex v2, double tolerance)
|
|
{
|
|
gp_Pnt p1 = BRep_Tool::Pnt(v1);
|
|
gp_Pnt p2 = BRep_Tool::Pnt(v2);
|
|
return p1.IsEqual(p2, tolerance);
|
|
}
|
|
|
|
bool DrawUtil::isZeroEdge(TopoDS_Edge e, double tolerance)
|
|
{
|
|
TopoDS_Vertex vStart = TopExp::FirstVertex(e);
|
|
TopoDS_Vertex vEnd = TopExp::LastVertex(e);
|
|
if (!isSamePoint(vStart, vEnd, tolerance)) {
|
|
return false;
|
|
}
|
|
|
|
//closed edge will have same V's but non-zero length
|
|
BRepAdaptor_Curve adapt(e);
|
|
double len = GCPnts_AbscissaPoint::Length(adapt, Precision::Confusion());
|
|
if (len > tolerance) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
double DrawUtil::simpleMinDist(TopoDS_Shape s1, TopoDS_Shape s2)
|
|
{
|
|
BRepExtrema_DistShapeShape extss(s1, s2);
|
|
if (!extss.IsDone()) {
|
|
Base::Console().Message("DU::simpleMinDist - BRepExtrema_DistShapeShape failed");
|
|
return -1;
|
|
}
|
|
int count = extss.NbSolution();
|
|
if (count != 0) {
|
|
return extss.Value();
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//! returns 2d vector's angle with X axis. result is [0, 2pi].
|
|
double DrawUtil::angleWithX(Base::Vector3d inVec)
|
|
{
|
|
double result = atan2(inVec.y, inVec.x);
|
|
if (result < 0) {
|
|
result += 2.0 * M_PI;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//! assumes 2d on XY
|
|
//! quick angle for straight edges
|
|
double DrawUtil::angleWithX(TopoDS_Edge e, bool reverse)
|
|
{
|
|
gp_Pnt gstart = BRep_Tool::Pnt(TopExp::FirstVertex(e));
|
|
Base::Vector3d start(gstart.X(), gstart.Y(), gstart.Z());
|
|
gp_Pnt gend = BRep_Tool::Pnt(TopExp::LastVertex(e));
|
|
Base::Vector3d end(gend.X(), gend.Y(), gend.Z());
|
|
Base::Vector3d u;
|
|
if (reverse) {
|
|
u = start - end;
|
|
} else {
|
|
u = end - start;
|
|
}
|
|
double result = atan2(u.y, u.x);
|
|
if (result < 0) {
|
|
result += 2.0 * M_PI;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//! find angle of edge with x-Axis at First/LastVertex
|
|
double DrawUtil::angleWithX(TopoDS_Edge e, TopoDS_Vertex v, double tolerance)
|
|
{
|
|
double param = 0;
|
|
|
|
BRepAdaptor_Curve adapt(e);
|
|
if (isFirstVert(e, v, tolerance)) {
|
|
param = adapt.FirstParameter();
|
|
} else if (isLastVert(e, v, tolerance)) {
|
|
param = adapt.LastParameter();
|
|
} else {
|
|
//TARFU
|
|
Base::Console().Message("Error: DU::angleWithX - v is neither first nor last \n");
|
|
}
|
|
gp_Pnt paramPoint;
|
|
gp_Vec derivative;
|
|
const Handle(Geom_Curve) c = adapt.Curve().Curve();
|
|
c->D1(param, paramPoint, derivative);
|
|
double angle = atan2(derivative.Y(), derivative.X());
|
|
if (angle < 0) {//map from [-PI:PI] to [0:2PI]
|
|
angle += 2.0 * M_PI;
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
//! find angle of incidence at First/LastVertex
|
|
double DrawUtil::incidenceAngleAtVertex(TopoDS_Edge e, TopoDS_Vertex v, double tolerance)
|
|
{
|
|
double incidenceAngle = 0;
|
|
|
|
BRepAdaptor_Curve adapt(e);
|
|
double paramRange = adapt.LastParameter() - adapt.FirstParameter();
|
|
double paramOffset = paramRange / 100.0;
|
|
double vertexParam;
|
|
Base::Vector3d anglePoint = DrawUtil::vertex2Vector(v);
|
|
Base::Vector3d offsetPoint, incidenceVec;
|
|
int noTangents = 0;
|
|
if (isFirstVert(e, v, tolerance)) {
|
|
vertexParam = adapt.FirstParameter();
|
|
BRepLProp_CLProps prop(
|
|
adapt, vertexParam + paramOffset, noTangents, Precision::Confusion());
|
|
const gp_Pnt& gOffsetPoint = prop.Value();
|
|
offsetPoint = Base::Vector3d(gOffsetPoint.X(), gOffsetPoint.Y(), gOffsetPoint.Z());
|
|
} else if (isLastVert(e, v, tolerance)) {
|
|
vertexParam = adapt.LastParameter();
|
|
BRepLProp_CLProps prop(
|
|
adapt, vertexParam - paramOffset, noTangents, Precision::Confusion());
|
|
const gp_Pnt& gOffsetPoint = prop.Value();
|
|
offsetPoint = Base::Vector3d(gOffsetPoint.X(), gOffsetPoint.Y(), gOffsetPoint.Z());
|
|
} else {
|
|
//TARFU
|
|
// Base::Console().Message("DU::incidenceAngle - v is neither first nor last \n");
|
|
}
|
|
incidenceVec = anglePoint - offsetPoint;
|
|
incidenceAngle = atan2(incidenceVec.y, incidenceVec.x);
|
|
|
|
//map to [0:2PI]
|
|
if (incidenceAngle < 0.0) {
|
|
incidenceAngle = M_2PI + incidenceAngle;
|
|
}
|
|
|
|
return incidenceAngle;
|
|
}
|
|
|
|
|
|
//! true if actualAngle(degrees) is within allowableError [0,360] of
|
|
//! targetAngle (degrees)
|
|
bool DrawUtil::isWithinRange(double actualAngleIn, double targetAngleIn, double allowableError)
|
|
{
|
|
constexpr double DegreesPerRevolution{360};
|
|
constexpr double DegreesPerHalfRevolution{180};
|
|
// map both angles from [0, 360] to [-180, 180]. This solves the problem of
|
|
// comparing angles near 0, such as 5deg & 355deg where the desired answer is
|
|
// 10, not 350;
|
|
double actualAngleDeg = actualAngleIn;
|
|
if (actualAngleDeg < DegreesPerRevolution &&
|
|
actualAngleDeg > DegreesPerHalfRevolution) {
|
|
actualAngleDeg = actualAngleDeg - DegreesPerRevolution;
|
|
}
|
|
|
|
double targetAngleDeg = targetAngleIn;
|
|
if (targetAngleDeg < DegreesPerRevolution &&
|
|
targetAngleDeg > DegreesPerHalfRevolution) {
|
|
targetAngleDeg = targetAngleDeg - DegreesPerRevolution;
|
|
}
|
|
|
|
double actualError = fabs(targetAngleDeg - actualAngleDeg);
|
|
if (actualError <= allowableError) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool DrawUtil::isFirstVert(TopoDS_Edge e, TopoDS_Vertex v, double tolerance)
|
|
{
|
|
TopoDS_Vertex first = TopExp::FirstVertex(e);
|
|
return isSamePoint(first, v, tolerance);
|
|
}
|
|
|
|
bool DrawUtil::isLastVert(TopoDS_Edge e, TopoDS_Vertex v, double tolerance)
|
|
{
|
|
TopoDS_Vertex last = TopExp::LastVertex(e);
|
|
return isSamePoint(last, v, tolerance);
|
|
}
|
|
|
|
bool DrawUtil::fpCompare(const double& d1, const double& d2, double tolerance)
|
|
{
|
|
return std::fabs(d1 - d2) < tolerance;
|
|
}
|
|
|
|
//brute force intersection points of line(point, dir) with box(xRange, yRange)
|
|
std::pair<Base::Vector3d, Base::Vector3d>
|
|
DrawUtil::boxIntersect2d(Base::Vector3d point, Base::Vector3d dirIn, double xRange, double yRange)
|
|
{
|
|
std::pair<Base::Vector3d, Base::Vector3d> result;
|
|
Base::Vector3d p1, p2;
|
|
Base::Vector3d dir = dirIn;
|
|
dir.Normalize();
|
|
// y = mx + b
|
|
// m = (y1 - y0) / (x1 - x0)
|
|
if (DrawUtil::fpCompare(dir.x, 0.0)) {//vertical case
|
|
p1 = Base::Vector3d(point.x, point.y - (yRange / 2.0), 0.0);
|
|
p2 = Base::Vector3d(point.x, point.y + (yRange / 2.0), 0.0);
|
|
} else {
|
|
double slope = dir.y / dir.x;
|
|
double left = -xRange / 2.0;
|
|
double right = xRange / 2.0;
|
|
if (DrawUtil::fpCompare(slope, 0.0)) {//horizontal case
|
|
p1 = Base::Vector3d(point.x - (xRange / 2.0), point.y);
|
|
p2 = Base::Vector3d(point.x + (xRange / 2.0), point.y);
|
|
} else {//normal case
|
|
double top = yRange / 2.0;
|
|
double bottom = -yRange / 2.0;
|
|
double yLeft = point.y - slope * (point.x - left);
|
|
double yRight = point.y - slope * (point.x - right);
|
|
double xTop = point.x - ((point.y - top) / slope);
|
|
double xBottom = point.x - ((point.y - bottom) / slope);
|
|
|
|
if ((bottom < yLeft) && (top > yLeft)) {
|
|
p1 = Base::Vector3d(left, yLeft);
|
|
} else if (yLeft <= bottom) {
|
|
p1 = Base::Vector3d(xBottom, bottom);
|
|
} else if (yLeft >= top) {
|
|
p1 = Base::Vector3d(xTop, top);
|
|
}
|
|
|
|
if ((bottom < yRight) && (top > yRight)) {
|
|
p2 = Base::Vector3d(right, yRight);
|
|
} else if (yRight <= bottom) {
|
|
p2 = Base::Vector3d(xBottom, bottom);
|
|
} else if (yRight >= top) {
|
|
p2 = Base::Vector3d(xTop, top);
|
|
}
|
|
}
|
|
}
|
|
result.first = p1;
|
|
result.second = p2;
|
|
Base::Vector3d dirCheck = p2 - p1;
|
|
dirCheck.Normalize();
|
|
if (!dir.IsEqual(dirCheck, 0.00001)) {
|
|
result.first = p2;
|
|
result.second = p1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//find the apparent intersection of 2 3d curves. We are only interested in curves that are lines, so we will have either 0 or 1
|
|
//apparent intersection. The intersection is "apparent" because the curve's progenator is a trimmed curve (line segment)
|
|
//NOTE: these curves do not have a location, so the intersection point does not respect the
|
|
//placement of the edges which spawned the curves. Use the apparentIntersection(edge, edge) method instead.
|
|
bool DrawUtil::apparentIntersection(const Handle(Geom_Curve) curve1,
|
|
const Handle(Geom_Curve) curve2, Base::Vector3d& result)
|
|
{
|
|
GeomAPI_ExtremaCurveCurve intersector(curve1, curve2);
|
|
if (intersector.NbExtrema() == 0 || intersector.LowerDistance() > EWTOLERANCE) {
|
|
//no intersection
|
|
return false;
|
|
}
|
|
//for our purposes, only one intersection point is required.
|
|
gp_Pnt p1, p2;
|
|
intersector.Points(1, p1, p2);
|
|
result = toVector3d(p1);
|
|
return true;
|
|
}
|
|
|
|
bool DrawUtil::apparentIntersection(TopoDS_Edge& edge0, TopoDS_Edge& edge1, gp_Pnt& intersect)
|
|
{
|
|
gp_Pnt gStart0 = BRep_Tool::Pnt(TopExp::FirstVertex(edge0));
|
|
gp_Pnt gEnd0 = BRep_Tool::Pnt(TopExp::LastVertex(edge0));
|
|
gp_Pnt gStart1 = BRep_Tool::Pnt(TopExp::FirstVertex(edge1));
|
|
gp_Pnt gEnd1 = BRep_Tool::Pnt(TopExp::LastVertex(edge1));
|
|
|
|
//intersection of 2 3d lines in point&direction form
|
|
//https://math.stackexchange.com/questions/270767/find-intersection-of-two-3d-lines
|
|
gp_Vec C(gStart0.XYZ());
|
|
gp_Vec D(gStart1.XYZ());
|
|
gp_Vec e(gEnd0.XYZ() - gStart0.XYZ());//direction of line0
|
|
gp_Vec f(gEnd1.XYZ() - gStart1.XYZ());//direction of line1
|
|
Base::Console().Message(
|
|
"DU::apparentInter - e: %s f: %s\n", formatVector(e).c_str(), formatVector(f).c_str());
|
|
|
|
//check for cases the algorithm doesn't handle well
|
|
gp_Vec C1(gEnd0.XYZ());
|
|
gp_Vec D1(gEnd1.XYZ());
|
|
if (C.IsEqual(D, EWTOLERANCE, EWTOLERANCE) || C.IsEqual(D1, EWTOLERANCE, EWTOLERANCE)) {
|
|
intersect = gp_Pnt(C.XYZ());
|
|
return true;
|
|
}
|
|
if (C1.IsEqual(D, EWTOLERANCE, EWTOLERANCE) || C1.IsEqual(D1, EWTOLERANCE, EWTOLERANCE)) {
|
|
intersect = gp_Pnt(C1.XYZ());
|
|
return true;
|
|
}
|
|
|
|
gp_Vec g(D - C);//between a point on each line
|
|
Base::Console().Message("DU::apparentInter - C: %s D: %s g: %s\n",
|
|
formatVector(C).c_str(),
|
|
formatVector(D).c_str(),
|
|
formatVector(g).c_str());
|
|
|
|
gp_Vec fxg = f.Crossed(g);
|
|
double h = fxg.Magnitude();
|
|
gp_Vec fxe = f.Crossed(e);
|
|
double k = fxe.Magnitude();
|
|
Base::Console().Message("DU::apparentInter - h: %.3f k: %.3f\n", h, k);
|
|
if (fpCompare(k, 0.0)) {
|
|
//no intersection
|
|
return false;
|
|
}
|
|
gp_Vec el = e * (h / k);
|
|
double pm = 1.0;
|
|
if (fpCompare(fxg.Dot(fxe), -1.0)) {
|
|
//opposite directions
|
|
pm = -1.0;
|
|
}
|
|
intersect = gp_Pnt(C.XYZ() + el.XYZ() * pm);
|
|
return true;
|
|
}
|
|
|
|
//find the intersection of 2 lines (point0, dir0) and (point1, dir1)
|
|
//existence of an intersection is not checked
|
|
bool DrawUtil::intersect2Lines3d(Base::Vector3d point0, Base::Vector3d dir0, Base::Vector3d point1,
|
|
Base::Vector3d dir1, Base::Vector3d& intersect)
|
|
{
|
|
Base::Vector3d g = point1 - point0;
|
|
Base::Vector3d fxg = dir1.Cross(g);
|
|
Base::Vector3d fxgn = fxg;
|
|
fxgn.Normalize();
|
|
Base::Vector3d fxe = dir1.Cross(dir0);
|
|
Base::Vector3d fxen = fxe;
|
|
fxen.Normalize();
|
|
Base::Vector3d dir0n = dir0;
|
|
dir0n.Normalize();
|
|
Base::Vector3d dir1n = dir1;
|
|
dir1n.Normalize();
|
|
if (fabs(dir0n.Dot(dir1n)) == 1.0) {
|
|
//parallel lines, no intersection
|
|
Base::Console().Message("DU::intersect2 - parallel lines, no intersection\n");
|
|
return false;
|
|
}
|
|
|
|
double scaler = fxg.Length() / fxe.Length();
|
|
double direction = -1.0;
|
|
if (fxgn == fxen) {
|
|
direction = 1.0;
|
|
}
|
|
|
|
intersect = point0 + dir0 * scaler * direction;
|
|
return true;
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::vertex2Vector(const TopoDS_Vertex& v)
|
|
{
|
|
gp_Pnt gp = BRep_Tool::Pnt(v);
|
|
return Base::Vector3d(gp.X(), gp.Y(), gp.Z());
|
|
}
|
|
|
|
// template specialization
|
|
//template <> // GCC BUG 85282, wanting this to be outside class body
|
|
std::string DrawUtil::formatVector(const Base::Vector3d& v)
|
|
{
|
|
std::stringstream builder;
|
|
builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals());
|
|
builder << " (" << v.x << ", " << v.y << ", " << v.z << ") ";
|
|
return builder.str();
|
|
}
|
|
//template std::string DrawUtil::formatVector<Base::Vector3d>(const Base::Vector3d &v);
|
|
|
|
//! compare 2 vectors for sorting - true if v1 < v2
|
|
//! precision::Confusion() is too strict for vertex - vertex comparisons
|
|
bool DrawUtil::vectorLess(const Base::Vector3d& v1, const Base::Vector3d& v2)
|
|
{
|
|
if ((v1 - v2).Length() > EWTOLERANCE) {//ie v1 != v2
|
|
if (!DrawUtil::fpCompare(v1.x, v2.x, 2.0 * EWTOLERANCE)) {
|
|
return (v1.x < v2.x);
|
|
} else if (!DrawUtil::fpCompare(v1.y, v2.y, 2.0 * EWTOLERANCE)) {
|
|
return (v1.y < v2.y);
|
|
} else {
|
|
return (v1.z < v2.z);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//! test for equality of two vertexes using the vectorLess comparator as used
|
|
//! in sorts and containers
|
|
bool DrawUtil::vertexEqual(TopoDS_Vertex& v1, TopoDS_Vertex& v2)
|
|
{
|
|
gp_Pnt gv1 = BRep_Tool::Pnt(v1);
|
|
gp_Pnt gv2 = BRep_Tool::Pnt(v2);
|
|
Base::Vector3d vv1(gv1.X(), gv1.Y(), gv1.Z());
|
|
Base::Vector3d vv2(gv2.X(), gv2.Y(), gv2.Z());
|
|
return vectorEqual(vv1, vv2);
|
|
}
|
|
|
|
//! test for equality of two vectors using the vectorLess comparator as used
|
|
//! in sorts and containers
|
|
bool DrawUtil::vectorEqual(Base::Vector3d& v1, Base::Vector3d& v2)
|
|
{
|
|
bool less1 = vectorLess(v1, v2);
|
|
bool less2 = vectorLess(v2, v1);
|
|
return !less1 && !less2;
|
|
}
|
|
|
|
//TODO: the next 2 could be templated
|
|
//construct a compound shape from a list of edges
|
|
TopoDS_Shape DrawUtil::vectorToCompound(std::vector<TopoDS_Edge> vecIn, bool invert)
|
|
{
|
|
BRep_Builder builder;
|
|
TopoDS_Compound compOut;
|
|
builder.MakeCompound(compOut);
|
|
for (auto& v : vecIn) {
|
|
builder.Add(compOut, v);
|
|
}
|
|
if (invert) {
|
|
return ShapeUtils::mirrorShape(compOut);
|
|
}
|
|
return compOut;
|
|
}
|
|
|
|
//construct a compound shape from a list of wires
|
|
TopoDS_Shape DrawUtil::vectorToCompound(std::vector<TopoDS_Wire> vecIn, bool invert)
|
|
{
|
|
BRep_Builder builder;
|
|
TopoDS_Compound compOut;
|
|
builder.MakeCompound(compOut);
|
|
for (auto& v : vecIn) {
|
|
builder.Add(compOut, v);
|
|
}
|
|
if (invert) {
|
|
return ShapeUtils::mirrorShape(compOut);
|
|
}
|
|
return compOut;
|
|
}
|
|
|
|
// construct a compound shape from a list of shapes
|
|
// this version needs a different name since edges/wires are shapes
|
|
TopoDS_Shape DrawUtil::shapeVectorToCompound(std::vector<TopoDS_Shape> vecIn, bool invert)
|
|
{
|
|
BRep_Builder builder;
|
|
TopoDS_Compound compOut;
|
|
builder.MakeCompound(compOut);
|
|
for (auto& v : vecIn) {
|
|
if (!v.IsNull()) {
|
|
builder.Add(compOut, v);
|
|
}
|
|
}
|
|
if (invert) {
|
|
return ShapeUtils::mirrorShape(compOut);
|
|
}
|
|
return compOut;
|
|
}
|
|
|
|
//constructs a list of edges from a shape
|
|
std::vector<TopoDS_Edge> DrawUtil::shapeToVector(TopoDS_Shape shapeIn)
|
|
{
|
|
std::vector<TopoDS_Edge> vectorOut;
|
|
TopExp_Explorer expl(shapeIn, TopAbs_EDGE);
|
|
for (; expl.More(); expl.Next()) {
|
|
vectorOut.push_back(TopoDS::Edge(expl.Current()));
|
|
}
|
|
return vectorOut;
|
|
}
|
|
|
|
//!convert fromPoint in coordinate system fromSystem to reference coordinate system
|
|
Base::Vector3d DrawUtil::toR3(const gp_Ax2& fromSystem, const Base::Vector3d& fromPoint)
|
|
{
|
|
gp_Pnt gFromPoint(fromPoint.x, fromPoint.y, fromPoint.z);
|
|
gp_Trsf T;
|
|
gp_Ax3 gRef;
|
|
gp_Ax3 gFrom(fromSystem);
|
|
T.SetTransformation(gFrom, gRef);
|
|
gp_Pnt gToPoint = gFromPoint.Transformed(T);
|
|
Base::Vector3d toPoint(gToPoint.X(), gToPoint.Y(), gToPoint.Z());
|
|
return toPoint;
|
|
}
|
|
|
|
//! check if two vectors are parallel. Vectors don't have to be unit vectors
|
|
bool DrawUtil::checkParallel(const Base::Vector3d v1, Base::Vector3d v2, double tolerance)
|
|
{
|
|
double dot = fabs(v1.Dot(v2));
|
|
double mag = v1.Length() * v2.Length();
|
|
return DrawUtil::fpCompare(dot, mag, tolerance);
|
|
}
|
|
|
|
//! rotate vector by angle radians around axis through org
|
|
Base::Vector3d DrawUtil::vecRotate(Base::Vector3d vec, double angle, Base::Vector3d axis,
|
|
Base::Vector3d org)
|
|
{
|
|
Base::Matrix4D xForm;
|
|
xForm.rotLine(org, axis, angle);
|
|
return Base::Vector3d(xForm * (vec));
|
|
}
|
|
|
|
gp_Vec DrawUtil::closestBasis(gp_Vec inVec)
|
|
{
|
|
return gp_Vec(to<gp_Dir>(closestBasis(toVector3d(inVec))));
|
|
}
|
|
|
|
//! returns stdX, stdY or stdZ.
|
|
Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v)
|
|
{
|
|
Base::Vector3d result(0.0, -1, 0);
|
|
Base::Vector3d stdX(1.0, 0.0, 0.0);
|
|
Base::Vector3d stdY(0.0, 1.0, 0.0);
|
|
Base::Vector3d stdZ(0.0, 0.0, 1.0);
|
|
Base::Vector3d stdXr(-1.0, 0.0, 0.0);
|
|
Base::Vector3d stdYr(0.0, -1.0, 0.0);
|
|
Base::Vector3d stdZr(0.0, 0.0, -1.0);
|
|
|
|
//first check if already a basis
|
|
if (v.Dot(stdX) == 1.0 || v.Dot(stdY) == 1.0 || v.Dot(stdZ) == 1.0) {
|
|
return v;
|
|
}
|
|
if (v.Dot(stdX) == -1.0 || v.Dot(stdY) == -1.0 || v.Dot(stdZ) == -1.0) {
|
|
return -v;
|
|
}
|
|
|
|
//not a basis. find smallest angle with a basis.
|
|
double angleX, angleY, angleZ, angleXr, angleYr, angleZr, angleMin;
|
|
angleX = stdX.GetAngle(v);
|
|
angleY = stdY.GetAngle(v);
|
|
angleZ = stdZ.GetAngle(v);
|
|
angleXr = stdXr.GetAngle(v);
|
|
angleYr = stdYr.GetAngle(v);
|
|
angleZr = stdZr.GetAngle(v);
|
|
|
|
angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr});
|
|
if (angleX == angleMin) {
|
|
return Base::Vector3d(1.0, 0.0, 0.0);
|
|
}
|
|
|
|
if (angleY == angleMin) {
|
|
return Base::Vector3d(0.0, 1.0, 0.0);
|
|
}
|
|
|
|
if (angleZ == angleMin) {
|
|
return Base::Vector3d(0.0, 0.0, 1.0);
|
|
}
|
|
|
|
if (angleXr == angleMin) {
|
|
return Base::Vector3d(1.0, 0.0, 0.0);
|
|
}
|
|
|
|
if (angleYr == angleMin) {
|
|
return Base::Vector3d(0.0, 1.0, 0.0);
|
|
}
|
|
|
|
if (angleZr == angleMin) {
|
|
return Base::Vector3d(0.0, 0.0, 1.0);
|
|
}
|
|
|
|
//should not get to here
|
|
return Base::Vector3d(1.0, 0.0, 0.0);
|
|
}
|
|
|
|
//! returns +/- stdX, stdY or stdZ.
|
|
Base::Vector3d DrawUtil::closestBasisOriented(Base::Vector3d v)
|
|
{
|
|
Base::Vector3d result(0.0, -1, 0);
|
|
Base::Vector3d stdX(1.0, 0.0, 0.0);
|
|
Base::Vector3d stdY(0.0, 1.0, 0.0);
|
|
Base::Vector3d stdZ(0.0, 0.0, 1.0);
|
|
Base::Vector3d stdXr(-1.0, 0.0, 0.0);
|
|
Base::Vector3d stdYr(0.0, -1.0, 0.0);
|
|
Base::Vector3d stdZr(0.0, 0.0, -1.0);
|
|
|
|
//first check if already a basis
|
|
if (v.Dot(stdX) == 1.0 || v.Dot(stdY) == 1.0 || v.Dot(stdZ) == 1.0) {
|
|
return v;
|
|
}
|
|
if (v.Dot(stdX) == -1.0 || v.Dot(stdY) == -1.0 || v.Dot(stdZ) == -1.0) {
|
|
return v;
|
|
}
|
|
|
|
//not a basis. find smallest angle with a basis.
|
|
double angleX, angleY, angleZ, angleXr, angleYr, angleZr, angleMin;
|
|
angleX = stdX.GetAngle(v);
|
|
angleY = stdY.GetAngle(v);
|
|
angleZ = stdZ.GetAngle(v);
|
|
angleXr = stdXr.GetAngle(v);
|
|
angleYr = stdYr.GetAngle(v);
|
|
angleZr = stdZr.GetAngle(v);
|
|
|
|
angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr});
|
|
if (angleX == angleMin) {
|
|
return Base::Vector3d(1.0, 0.0, 0.0);
|
|
}
|
|
|
|
if (angleY == angleMin) {
|
|
return Base::Vector3d(0.0, 1.0, 0.0);
|
|
}
|
|
|
|
if (angleZ == angleMin) {
|
|
return Base::Vector3d(0.0, 0.0, 1.0);
|
|
}
|
|
|
|
if (angleXr == angleMin) {
|
|
return Base::Vector3d(-1.0, 0.0, 0.0);
|
|
}
|
|
|
|
if (angleYr == angleMin) {
|
|
return Base::Vector3d(0.0, -1.0, 0.0);
|
|
}
|
|
|
|
if (angleZr == angleMin) {
|
|
return Base::Vector3d(0.0, 0.0, -1.0);
|
|
}
|
|
|
|
//should not get to here
|
|
return Base::Vector3d(1.0, 0.0, 0.0);
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::closestBasis(Base::Vector3d vDir, gp_Ax2 coordSys)
|
|
{
|
|
gp_Dir gDir(vDir.x, vDir.y, vDir.z);
|
|
return closestBasis(gDir, coordSys);
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::closestBasis(gp_Dir gDir, gp_Ax2 coordSys)
|
|
{
|
|
gp_Dir xCS = coordSys.XDirection();
|
|
gp_Dir yCS = coordSys.YDirection();
|
|
gp_Dir zCS = coordSys.Direction();
|
|
|
|
//first check if already a basis
|
|
if (gDir.Dot(xCS) == 1.0 || gDir.Dot(yCS) == 1.0 || gDir.Dot(zCS) == 1.0) {
|
|
//gDir is parallel with a basis
|
|
return Base::Vector3d(gDir.X(), gDir.Y(), gDir.Z());
|
|
}
|
|
|
|
if (gDir.Dot(xCS.Reversed()) == 1.0 || gDir.Dot(yCS.Reversed()) == 1.0
|
|
|| gDir.Dot(zCS.Reversed()) == 1.0) {
|
|
//gDir is anti-parallel with a basis
|
|
return Base::Vector3d(-gDir.X(), -gDir.Y(), -gDir.Z());
|
|
}
|
|
|
|
//not a basis. find smallest angle with a basis.
|
|
double angleX, angleY, angleZ, angleXr, angleYr, angleZr, angleMin;
|
|
angleX = gDir.Angle(xCS);
|
|
angleY = gDir.Angle(yCS);
|
|
angleZ = gDir.Angle(zCS);
|
|
angleXr = gDir.Angle(xCS.Reversed());
|
|
angleYr = gDir.Angle(yCS.Reversed());
|
|
angleZr = gDir.Angle(zCS.Reversed());
|
|
|
|
angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr});
|
|
if (angleX == angleMin) {
|
|
return Base::Vector3d(xCS.X(), xCS.Y(), xCS.Z());
|
|
}
|
|
|
|
if (angleY == angleMin) {
|
|
return Base::Vector3d(yCS.X(), yCS.Y(), yCS.Z());
|
|
}
|
|
|
|
if (angleZ == angleMin) {
|
|
return Base::Vector3d(zCS.X(), zCS.Y(), zCS.Z());
|
|
}
|
|
|
|
if (angleXr == angleMin) {
|
|
return Base::Vector3d(-xCS.X(), -xCS.Y(), -xCS.Z());
|
|
}
|
|
|
|
if (angleYr == angleMin) {
|
|
return Base::Vector3d(-yCS.X(), -yCS.Y(), -yCS.Z());
|
|
}
|
|
|
|
if (angleZr == angleMin) {
|
|
return Base::Vector3d(-zCS.X(), -zCS.Y(), -zCS.Z());
|
|
}
|
|
|
|
//should not get to here
|
|
return Base::Vector3d(xCS.X(), xCS.Y(), xCS.Z());
|
|
}
|
|
|
|
//! find the size of a shape measured in a given (cardinal) direction
|
|
double DrawUtil::getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape)
|
|
{
|
|
Base::Vector3d stdX(1.0, 0.0, 0.0);
|
|
Base::Vector3d stdY(0.0, 1.0, 0.0);
|
|
Base::Vector3d stdZ(0.0, 0.0, 1.0);
|
|
Base::Vector3d stdXr(-1.0, 0.0, 0.0);
|
|
Base::Vector3d stdYr(0.0, -1.0, 0.0);
|
|
Base::Vector3d stdZr(0.0, 0.0, -1.0);
|
|
Base::Vector3d vClosestBasis = closestBasis(toVector3d(direction));
|
|
|
|
Bnd_Box shapeBox;
|
|
shapeBox.SetGap(0.0);
|
|
BRepBndLib::AddOptimal(shape, shapeBox);
|
|
double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0;
|
|
if (shapeBox.IsVoid()) {
|
|
//this really shouldn't happen here as null shapes should have been caught
|
|
//long before this
|
|
Base::Console().Error("DU::getWidthInDirection - shapeBox is void\n");
|
|
return 0.0;
|
|
}
|
|
shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
|
|
|
|
if (vClosestBasis.IsEqual(stdX, EWTOLERANCE) || vClosestBasis.IsEqual(stdXr, EWTOLERANCE)) {
|
|
return xMax - xMin;
|
|
}
|
|
|
|
if (vClosestBasis.IsEqual(stdY, EWTOLERANCE) || vClosestBasis.IsEqual(stdYr, EWTOLERANCE)) {
|
|
return yMax - yMin;
|
|
}
|
|
|
|
if (vClosestBasis.IsEqual(stdZ, EWTOLERANCE) || vClosestBasis.IsEqual(stdZr, EWTOLERANCE)) {
|
|
return zMax - zMin;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
//! mask off one component of the input vector. input vector (a, b, c) with
|
|
//! direction to mask (0, 1, 0) would return (a, 0.0, c). The mask is a
|
|
//! cardinal direction or the reverse of a cardinal direction.
|
|
gp_Vec DrawUtil::maskDirection(gp_Vec inVec, gp_Dir directionToMask)
|
|
{
|
|
if (fpCompare(std::fabs(directionToMask.Dot(gp::OX().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
|
return {0.0, inVec.Y(), inVec.Z()};
|
|
}
|
|
|
|
if (fpCompare(std::fabs(directionToMask.Dot(gp::OY().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
|
return {inVec.X(), 0.0, inVec.Z()};
|
|
}
|
|
|
|
if (fpCompare(std::fabs(directionToMask.Dot(gp::OZ().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
|
return {inVec.X(), inVec.Y(), 0.0};
|
|
}
|
|
|
|
Base::Console().Message("DU:maskDirection - directionToMask is not cardinal\n");
|
|
return {};
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::maskDirection(Base::Vector3d inVec, Base::Vector3d directionToMask)
|
|
{
|
|
return toVector3d(maskDirection(to<gp_Vec>(inVec), to<gp_Vec>(directionToMask)));
|
|
}
|
|
|
|
//! get the coordinate of inPoint for the cardinal unit direction.
|
|
double DrawUtil::coordinateForDirection(Base::Vector3d inPoint, Base::Vector3d cardinal)
|
|
{
|
|
auto masked = maskDirection(inPoint, cardinal);
|
|
auto stripped = inPoint - masked;
|
|
return stripped.x + stripped.y + stripped.z;
|
|
}
|
|
|
|
//based on Function provided by Joe Dowsett, 2014
|
|
double DrawUtil::sensibleScale(double working_scale)
|
|
{
|
|
if (!(working_scale > 0.0)) {
|
|
return 1.0;
|
|
}
|
|
//which gives the largest scale for which the min_space requirements can be met, but we want a 'sensible' scale, rather than 0.28457239...
|
|
//eg if working_scale = 0.115, then we want to use 0.1, similarly 7.65 -> 5, and 76.5 -> 50
|
|
|
|
float exponent = std::floor(std::log10(working_scale));//if working_scale = a * 10^b, what is b?
|
|
working_scale *= std::pow(10, -exponent); //now find what 'a' is.
|
|
|
|
//int choices = 10;
|
|
float valid_scales[2][10] = {{1.0,
|
|
1.25,
|
|
2.0,
|
|
2.5,
|
|
3.75,
|
|
5.0,
|
|
7.5,
|
|
10.0,
|
|
50.0,
|
|
100.0},//equate to 1:10, 1:8, 1:5, 1:4, 3:8, 1:2, 3:4, 1:1
|
|
// .1 .125 .375 .75
|
|
{1.0,
|
|
1.5,
|
|
2.0,
|
|
3.0,
|
|
4.0,
|
|
5.0,
|
|
8.0,
|
|
10.0,
|
|
50.0,
|
|
100.0}};//equate to 1:1, 3:2, 2:1, 3:1, 4:1, 5:1, 8:1, 10:1
|
|
// 1.5:1
|
|
//int i = choices - 1;
|
|
int i = 9;
|
|
while (valid_scales[(exponent >= 0)][i]
|
|
> working_scale) {//choose closest value smaller than 'a' from list.
|
|
i -= 1; //choosing top list if exponent -ve, bottom list for +ve exponent
|
|
}
|
|
|
|
//now have the appropriate scale, reapply the *10^b
|
|
return valid_scales[(exponent >= 0)][i] * pow(10, exponent);
|
|
}
|
|
|
|
double DrawUtil::getDefaultLineWeight(std::string lineType)
|
|
{
|
|
return TechDraw::LineGroup::getDefaultWidth(lineType);
|
|
}
|
|
|
|
bool DrawUtil::isBetween(const Base::Vector3d pt, const Base::Vector3d end1,
|
|
const Base::Vector3d end2)
|
|
{
|
|
double segLength = (end2 - end1).Length();
|
|
double l1 = (pt - end1).Length();
|
|
double l2 = (pt - end2).Length();
|
|
if (fpCompare(segLength, l1 + l2)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::Intersect2d(Base::Vector3d p1, Base::Vector3d d1, Base::Vector3d p2,
|
|
Base::Vector3d d2)
|
|
{
|
|
Base::Vector3d p12(p1.x + d1.x, p1.y + d1.y, 0.0);
|
|
double A1 = d1.y;
|
|
double B1 = -d1.x;
|
|
double C1 = A1 * p1.x + B1 * p1.y;
|
|
|
|
Base::Vector3d p22(p2.x + d2.x, p2.y + d2.y, 0.0);
|
|
double A2 = d2.y;
|
|
double B2 = -d2.x;
|
|
double C2 = A2 * p2.x + B2 * p2.y;
|
|
|
|
double det = A1 * B2 - A2 * B1;
|
|
if (fpCompare(det, 0.0, Precision::Confusion())) {
|
|
Base::Console().Message("Lines are parallel\n");
|
|
return Base::Vector3d(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
double x = (B2 * C1 - B1 * C2) / det;
|
|
double y = (A1 * C2 - A2 * C1) / det;
|
|
return Base::Vector3d(x, y, 0.0);
|
|
}
|
|
|
|
Base::Vector2d DrawUtil::Intersect2d(Base::Vector2d p1, Base::Vector2d d1, Base::Vector2d p2,
|
|
Base::Vector2d d2)
|
|
{
|
|
Base::Vector2d p12(p1.x + d1.x, p1.y + d1.y);
|
|
double A1 = d1.y;
|
|
double B1 = -d1.x;
|
|
double C1 = A1 * p1.x + B1 * p1.y;
|
|
|
|
Base::Vector2d p22(p2.x + d2.x, p2.y + d2.y);
|
|
double A2 = d2.y;
|
|
double B2 = -d2.x;
|
|
double C2 = A2 * p2.x + B2 * p2.y;
|
|
|
|
double det = A1 * B2 - A2 * B1;
|
|
if (fpCompare(det, 0.0, Precision::Confusion())) {
|
|
Base::Console().Message("Lines are parallel\n");
|
|
return Base::Vector2d(0.0, 0.0);
|
|
}
|
|
|
|
double x = (B2 * C1 - B1 * C2) / det;
|
|
double y = (A1 * C2 - A2 * C1) / det;
|
|
return Base::Vector2d(x, y);
|
|
}
|
|
|
|
|
|
std::string DrawUtil::shapeToString(TopoDS_Shape s)
|
|
{
|
|
std::ostringstream buffer;
|
|
BRepTools::Write(s, buffer);
|
|
return buffer.str();
|
|
}
|
|
|
|
TopoDS_Shape DrawUtil::shapeFromString(std::string s)
|
|
{
|
|
TopoDS_Shape result;
|
|
BRep_Builder builder;
|
|
std::istringstream buffer(s);
|
|
BRepTools::Read(result, buffer, builder);
|
|
return result;
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::invertY(Base::Vector3d v)
|
|
{
|
|
return Base::Vector3d(v.x, -v.y, v.z);
|
|
}
|
|
|
|
QPointF DrawUtil::invertY(QPointF v)
|
|
{
|
|
return QPointF(v.x(), -v.y());
|
|
}
|
|
|
|
//! convert a gui point into its app space equivalent. this requires us to
|
|
//! perform the invert, scale, rotate operations in the reverse order from
|
|
//! that used to generate the qt point.
|
|
//! Note: the centering operation is not considered here
|
|
Base::Vector3d DrawUtil::toAppSpace(const DrawViewPart& dvp, const Base::Vector3d &qtPoint)
|
|
{
|
|
// Base::Console().Message("DGU::toPaperSpace(%s)\n", formatVector(qtPoint).c_str());
|
|
// From Y+ is down to Y+ is up
|
|
Base::Vector3d appPoint = invertY(qtPoint);
|
|
|
|
// remove the effect of the Rotation property
|
|
double rotDeg = dvp.Rotation.getValue();
|
|
double rotRad = rotDeg * M_PI / 180.0;
|
|
if (rotDeg != 0.0) {
|
|
// we always rotate around the origin.
|
|
appPoint.RotateZ(-rotRad);
|
|
}
|
|
|
|
// convert to 1:1 scale
|
|
appPoint = appPoint / dvp.getScale();
|
|
|
|
return appPoint;
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::toAppSpace(const DrawViewPart& dvp, const QPointF& qtPoint)
|
|
{
|
|
return toAppSpace(dvp, toVector3d(qtPoint));
|
|
}
|
|
|
|
//obs? was used in CSV prototype of Cosmetics
|
|
std::vector<std::string> DrawUtil::split(std::string csvLine)
|
|
{
|
|
// Base::Console().Message("DU::split - csvLine: %s\n", csvLine.c_str());
|
|
std::vector<std::string> result;
|
|
std::stringstream lineStream(csvLine);
|
|
std::string cell;
|
|
|
|
while (std::getline(lineStream, cell, ',')) {
|
|
result.push_back(cell);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//obs? was used in CSV prototype of Cosmetics
|
|
std::vector<std::string> DrawUtil::tokenize(std::string csvLine, std::string delimiter)
|
|
{
|
|
// Base::Console().Message("DU::tokenize - csvLine: %s delimit: %s\n", csvLine.c_str(), delimiter.c_str());
|
|
std::string s(csvLine);
|
|
size_t pos = 0;
|
|
std::vector<std::string> tokens;
|
|
while ((pos = s.find(delimiter)) != std::string::npos) {
|
|
tokens.push_back(s.substr(0, pos));
|
|
s.erase(0, pos + delimiter.length());
|
|
}
|
|
if (!s.empty()) {
|
|
tokens.push_back(s);
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
Base::Color DrawUtil::pyTupleToColor(PyObject* pColor)
|
|
{
|
|
// Base::Console().Message("DU::pyTupleToColor()\n");
|
|
double red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
|
|
if (!PyTuple_Check(pColor)) {
|
|
return Base::Color(red, green, blue, alpha);
|
|
}
|
|
|
|
int tSize = (int)PyTuple_Size(pColor);
|
|
if (tSize > 2) {
|
|
PyObject* pRed = PyTuple_GetItem(pColor, 0);
|
|
red = PyFloat_AsDouble(pRed);
|
|
PyObject* pGreen = PyTuple_GetItem(pColor, 1);
|
|
green = PyFloat_AsDouble(pGreen);
|
|
PyObject* pBlue = PyTuple_GetItem(pColor, 2);
|
|
blue = PyFloat_AsDouble(pBlue);
|
|
}
|
|
if (tSize > 3) {
|
|
PyObject* pAlpha = PyTuple_GetItem(pColor, 3);
|
|
alpha = PyFloat_AsDouble(pAlpha);
|
|
}
|
|
return Base::Color(red, green, blue, alpha);
|
|
}
|
|
|
|
PyObject* DrawUtil::colorToPyTuple(Base::Color color)
|
|
{
|
|
// Base::Console().Message("DU::pyTupleToColor()\n");
|
|
PyObject* pTuple = PyTuple_New(4);
|
|
PyObject* pRed = PyFloat_FromDouble(color.r);
|
|
PyObject* pGreen = PyFloat_FromDouble(color.g);
|
|
PyObject* pBlue = PyFloat_FromDouble(color.b);
|
|
PyObject* pAlpha = PyFloat_FromDouble(color.a);
|
|
|
|
PyTuple_SET_ITEM(pTuple, 0, pRed);
|
|
PyTuple_SET_ITEM(pTuple, 1, pGreen);
|
|
PyTuple_SET_ITEM(pTuple, 2, pBlue);
|
|
PyTuple_SET_ITEM(pTuple, 3, pAlpha);
|
|
|
|
return pTuple;
|
|
}
|
|
|
|
//check for crazy edge. This is probably a geometry error of some sort.
|
|
// note that cosmetic edges are stored as unscaled, so this test will be checking 1:1 lengths.
|
|
// a 1:1 length of > 10m is perfectly reasonable, so this check causes trouble with cosmetics.
|
|
bool DrawUtil::isCrazy(TopoDS_Edge e)
|
|
{
|
|
|
|
if (e.IsNull()) {
|
|
return true;
|
|
}
|
|
|
|
bool crazyOK = Preferences::getPreferenceGroup("debug")->GetBool("allowCrazyEdge", false);
|
|
if (crazyOK) {
|
|
return false;
|
|
}
|
|
|
|
BRepAdaptor_Curve adapt(e);
|
|
|
|
double edgeLength = GCPnts_AbscissaPoint::Length(adapt, Precision::Confusion());
|
|
if (edgeLength < 0.00001) {//edge is scaled. this is 0.00001 mm on paper
|
|
return true;
|
|
}
|
|
if (edgeLength > 9999.9) {//edge is scaled. this is 10 m on paper. can't be right?
|
|
return true;
|
|
}
|
|
|
|
double start = BRepLProp_CurveTool::FirstParameter(adapt);
|
|
double end = BRepLProp_CurveTool::LastParameter(adapt);
|
|
BRepLProp_CLProps propStart(adapt, start, 0, Precision::Confusion());
|
|
const gp_Pnt& vStart = propStart.Value();
|
|
BRepLProp_CLProps propEnd(adapt, end, 0, Precision::Confusion());
|
|
const gp_Pnt& vEnd = propEnd.Value();
|
|
double distance = vStart.Distance(vEnd);
|
|
double ratio = edgeLength / distance;
|
|
if (adapt.GetType() == GeomAbs_BSplineCurve && distance > 0.001 &&// not a closed loop
|
|
ratio > 9999.9) { // 10, 000x
|
|
return true; //this is crazy edge
|
|
} else if (adapt.GetType() == GeomAbs_Ellipse) {
|
|
gp_Elips ellp = adapt.Ellipse();
|
|
double major = ellp.MajorRadius();
|
|
double minor = ellp.MinorRadius();
|
|
if (minor < 0.001) {//too narrow
|
|
return true;
|
|
} else if (major > 9999.9) {//too big
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Base::Console().Message("DU::isCrazy - returns: %d ratio: %.3f\n", false, ratio);
|
|
return false;
|
|
}
|
|
|
|
//get 3d position of a face's center
|
|
Base::Vector3d DrawUtil::getFaceCenter(TopoDS_Face f)
|
|
{
|
|
BRepAdaptor_Surface adapt(f);
|
|
double u1 = adapt.FirstUParameter();
|
|
double u2 = adapt.LastUParameter();
|
|
double mu = (u1 + u2) / 2.0;
|
|
double v1 = adapt.FirstVParameter();
|
|
double v2 = adapt.LastVParameter();
|
|
double mv = (v1 + v2) / 2.0;
|
|
BRepLProp_SLProps prop(adapt, mu, mv, 0, Precision::Confusion());
|
|
const gp_Pnt gv = prop.Value();
|
|
return Base::Vector3d(gv.X(), gv.Y(), gv.Z());
|
|
}
|
|
|
|
// test the circulation of the triangle A-B-C
|
|
bool DrawUtil::circulation(Base::Vector3d A, Base::Vector3d B, Base::Vector3d C)
|
|
{
|
|
if (A.x * B.y + A.y * C.x + B.x * C.y - C.x * B.y - C.y * A.x - B.x * A.y > 0.0) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Base::Vector3d DrawUtil::getTrianglePoint(Base::Vector3d p1, Base::Vector3d dir, Base::Vector3d p2)
|
|
{
|
|
// get third point of a perpendicular triangle
|
|
// p1, p2 ...vertexes of hypothenusis, dir ...direction of one kathete, p3 ...3rd vertex
|
|
float a = -dir.y;
|
|
float b = dir.x;
|
|
float c1 = p1.x * a + p1.y * b;
|
|
float c2 = -p2.x * b + p2.y * a;
|
|
float ab = a * a + b * b;
|
|
float x = (c1 * a - c2 * b) / ab;
|
|
float y = (c2 * a + c1 * b) / ab;
|
|
Base::Vector3d p3(x,y,0.0);
|
|
return p3;
|
|
}
|
|
|
|
int DrawUtil::countSubShapes(TopoDS_Shape shape, TopAbs_ShapeEnum subShape)
|
|
{
|
|
int count = 0;
|
|
TopExp_Explorer Ex(shape, subShape);
|
|
while (Ex.More()) {
|
|
count++;
|
|
Ex.Next();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//https://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string
|
|
//for every character in inoutText, check if it is on the list of characters to be substituted and
|
|
//replace it with the encoding string
|
|
void DrawUtil::encodeXmlSpecialChars(std::string& inoutText)
|
|
{
|
|
std::string buffer;
|
|
buffer.reserve(inoutText.size());
|
|
for (size_t cursor = 0; cursor != inoutText.size(); ++cursor) {
|
|
switch (inoutText.at(cursor)) {
|
|
case '&':
|
|
buffer.append("&");
|
|
break;
|
|
case '\"':
|
|
buffer.append(""");
|
|
break;
|
|
case '\'':
|
|
buffer.append("'");
|
|
break;
|
|
case '<':
|
|
buffer.append("<");
|
|
break;
|
|
case '>':
|
|
buffer.append(">");
|
|
break;
|
|
default:
|
|
buffer.append(&inoutText.at(cursor), 1);//not a special character
|
|
break;
|
|
}
|
|
}
|
|
inoutText.swap(buffer);
|
|
}
|
|
|
|
//Sort edges into nose to tail order. From Part/App/AppPartPy.cpp. gives back a sequence
|
|
//of nose to tail edges and a shrunken input sequence of edges (the unconnected left overs)
|
|
//struct EdgePoints {
|
|
// gp_Pnt v1, v2;
|
|
// std::list<TopoDS_Edge>::iterator it;
|
|
// TopoDS_Edge edge;
|
|
//};
|
|
std::list<TopoDS_Edge> DrawUtil::sort_Edges(double tol3d, std::list<TopoDS_Edge>& edges)
|
|
{
|
|
tol3d = tol3d * tol3d;
|
|
std::list<EdgePoints> edge_points;
|
|
TopExp_Explorer xp;
|
|
for (std::list<TopoDS_Edge>::iterator it = edges.begin(); it != edges.end(); ++it) {
|
|
EdgePoints ep;
|
|
xp.Init(*it, TopAbs_VERTEX);
|
|
ep.v1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current()));
|
|
xp.Next();
|
|
ep.v2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current()));
|
|
ep.it = it;
|
|
ep.edge = *it;
|
|
edge_points.push_back(ep);
|
|
}
|
|
|
|
if (edge_points.empty()) {
|
|
return std::list<TopoDS_Edge>();
|
|
}
|
|
|
|
std::list<TopoDS_Edge> sorted;
|
|
gp_Pnt gpChainFirst, gpChainLast;
|
|
gpChainFirst = edge_points.front().v1;
|
|
gpChainLast = edge_points.front().v2;
|
|
|
|
sorted.push_back(edge_points.front().edge);
|
|
edges.erase(edge_points.front().it);
|
|
edge_points.erase(edge_points.begin());
|
|
|
|
while (!edge_points.empty()) {
|
|
// search for adjacent edge
|
|
std::list<EdgePoints>::iterator itEdgePoint;
|
|
for (itEdgePoint = edge_points.begin(); itEdgePoint != edge_points.end(); ++itEdgePoint) {
|
|
if (itEdgePoint->v1.SquareDistance(gpChainLast) <= tol3d) {
|
|
//found a connection from end of chain to start of edge
|
|
gpChainLast = itEdgePoint->v2;
|
|
sorted.push_back(itEdgePoint->edge);
|
|
edges.erase(itEdgePoint->it);
|
|
edge_points.erase(itEdgePoint);
|
|
itEdgePoint = edge_points.begin();
|
|
break;
|
|
} else if (itEdgePoint->v2.SquareDistance(gpChainFirst) <= tol3d) {
|
|
//found a connection from start of chain to end of edge
|
|
gpChainFirst = itEdgePoint->v1;
|
|
sorted.push_front(itEdgePoint->edge);
|
|
edges.erase(itEdgePoint->it);
|
|
edge_points.erase(itEdgePoint);
|
|
itEdgePoint = edge_points.begin();
|
|
break;
|
|
} else if (itEdgePoint->v2.SquareDistance(gpChainLast) <= tol3d) {
|
|
//found a connection from end of chain to end of edge
|
|
gpChainLast = itEdgePoint->v1;
|
|
Standard_Real firstParam, lastParam;
|
|
const Handle(Geom_Curve)& curve =
|
|
BRep_Tool::Curve(itEdgePoint->edge, firstParam, lastParam);
|
|
firstParam = curve->ReversedParameter(firstParam);
|
|
lastParam = curve->ReversedParameter(lastParam);
|
|
TopoDS_Edge edgeReversed =
|
|
BRepBuilderAPI_MakeEdge(curve->Reversed(), firstParam, lastParam);
|
|
sorted.push_back(edgeReversed);
|
|
edges.erase(itEdgePoint->it);
|
|
edge_points.erase(itEdgePoint);
|
|
itEdgePoint = edge_points.begin();
|
|
break;
|
|
} else if (itEdgePoint->v1.SquareDistance(gpChainFirst) <= tol3d) {
|
|
//found a connection from start of chain to start of edge
|
|
gpChainFirst = itEdgePoint->v2;
|
|
Standard_Real firstParam, lastParam;
|
|
const Handle(Geom_Curve)& curve =
|
|
BRep_Tool::Curve(itEdgePoint->edge, firstParam, lastParam);
|
|
firstParam = curve->ReversedParameter(firstParam);
|
|
lastParam = curve->ReversedParameter(lastParam);
|
|
TopoDS_Edge edgeReversed =
|
|
BRepBuilderAPI_MakeEdge(curve->Reversed(), firstParam, lastParam);
|
|
sorted.push_front(edgeReversed);
|
|
edges.erase(itEdgePoint->it);
|
|
edge_points.erase(itEdgePoint);
|
|
itEdgePoint = edge_points.begin();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (itEdgePoint == edge_points.end()
|
|
|| gpChainLast.SquareDistance(gpChainFirst) <= tol3d) {
|
|
// no adjacent edge found or polyline is closed
|
|
return sorted;
|
|
}
|
|
}
|
|
|
|
return sorted;
|
|
}
|
|
|
|
// Supplementary mathematical functions
|
|
// ====================================
|
|
|
|
int DrawUtil::sgn(double x)
|
|
{
|
|
return (x > +Precision::Confusion()) - (x < -Precision::Confusion());
|
|
}
|
|
|
|
double DrawUtil::sqr(double x)
|
|
{
|
|
return x * x;
|
|
}
|
|
|
|
void DrawUtil::angleNormalize(double& fi)
|
|
{
|
|
while (fi <= -M_PI) {
|
|
fi += M_2PI;
|
|
}
|
|
while (fi > M_PI) {
|
|
fi -= M_2PI;
|
|
}
|
|
}
|
|
|
|
double DrawUtil::angleComposition(double fi, double delta)
|
|
{
|
|
fi += delta;
|
|
|
|
angleNormalize(fi);
|
|
return fi;
|
|
}
|
|
|
|
double DrawUtil::angleDifference(double fi1, double fi2, bool reflex)
|
|
{
|
|
angleNormalize(fi1);
|
|
angleNormalize(fi2);
|
|
|
|
fi1 -= fi2;
|
|
|
|
if ((fi1 > +M_PI || fi1 <= -M_PI) != reflex) {
|
|
fi1 += fi1 > 0.0 ? -M_2PI : +M_2PI;
|
|
}
|
|
|
|
return fi1;
|
|
}
|
|
|
|
std::pair<int, int> DrawUtil::nearestFraction(double val, int maxDenom)
|
|
{
|
|
// Find rational approximation to given real number
|
|
// David Eppstein / UC Irvine / 8 Aug 1993
|
|
//
|
|
// With corrections from Arno Formella, May 2008
|
|
// and additional fiddles by WF 2017
|
|
// usage: a.out r d
|
|
// r is real number to approx
|
|
// d is the maximum denominator allowed
|
|
//
|
|
// Based on the theory of continued fractions
|
|
// if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
|
|
// then best approximation is found by truncating this series
|
|
// (with some adjustments in the last term).
|
|
//
|
|
// Note the fraction can be recovered as the first column of the matrix
|
|
// ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
|
|
// ( 1 0 ) ( 1 0 ) ( 1 0 )
|
|
// Instead of keeping the sequence of continued fraction terms,
|
|
// we just keep the last partial product of these matrices.
|
|
std::pair<int, int> result;
|
|
long m[2][2];
|
|
long maxden = maxDenom;
|
|
long ai;
|
|
double x = val;
|
|
double startx = x;
|
|
|
|
/* initialize matrix */
|
|
m[0][0] = m[1][1] = 1;
|
|
m[0][1] = m[1][0] = 0;
|
|
|
|
/* loop finding terms until denom gets too big */
|
|
while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) {
|
|
long t;
|
|
t = m[0][0] * ai + m[0][1];
|
|
m[0][1] = m[0][0];
|
|
m[0][0] = t;
|
|
t = m[1][0] * ai + m[1][1];
|
|
m[1][1] = m[1][0];
|
|
m[1][0] = t;
|
|
if(x == (double) ai)
|
|
break; // AF: division by zero
|
|
x = 1/(x - (double) ai);
|
|
if(x > (double) std::numeric_limits<int>::max())
|
|
break; // AF: representation failure
|
|
}
|
|
|
|
/* now remaining x is between 0 and 1/ai */
|
|
/* approx as either 0 or 1/m where m is max that will fit in maxden */
|
|
/* first try zero */
|
|
double error1 = startx - ((double) m[0][0] / (double) m[1][0]);
|
|
int n1 = m[0][0];
|
|
int d1 = m[1][0];
|
|
|
|
/* now try other possibility */
|
|
ai = (maxden - m[1][1]) / m[1][0];
|
|
m[0][0] = m[0][0] * ai + m[0][1];
|
|
m[1][0] = m[1][0] * ai + m[1][1];
|
|
double error2 = startx - ((double) m[0][0] / (double) m[1][0]);
|
|
int n2 = m[0][0];
|
|
int d2 = m[1][0];
|
|
|
|
if (std::fabs(error1) <= std::fabs(error2)) {
|
|
result.first = n1;
|
|
result.second = d1;
|
|
} else {
|
|
result.first = n2;
|
|
result.second = d2;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Interval marking functions
|
|
// ==========================
|
|
|
|
unsigned int DrawUtil::intervalMerge(std::vector<std::pair<double, bool>>& marking, double boundary,
|
|
bool wraps)
|
|
{
|
|
// We will be returning the placement index instead of an iterator, because indices
|
|
// are still valid after we insert on higher positions, while iterators may be invalidated
|
|
// due to the insertion triggered reallocation
|
|
unsigned int i = 0;
|
|
bool last = false;
|
|
|
|
if (wraps && !marking.empty()) {
|
|
last = marking.back().second;
|
|
}
|
|
|
|
while (i < marking.size()) {
|
|
if (marking[i].first == boundary) {
|
|
return i;
|
|
}
|
|
if (marking[i].first > boundary) {
|
|
break;
|
|
}
|
|
|
|
last = marking[i].second;
|
|
++i;
|
|
}
|
|
|
|
if (!wraps && i >= marking.size()) {
|
|
last = false;
|
|
}
|
|
|
|
marking.insert(marking.begin() + i, std::pair<double, bool>(boundary, last));
|
|
return i;
|
|
}
|
|
|
|
void DrawUtil::intervalMarkLinear(std::vector<std::pair<double, bool>>& marking, double start,
|
|
double length, bool value)
|
|
{
|
|
if (length == 0.0) {
|
|
return;
|
|
}
|
|
if (length < 0.0) {
|
|
length = -length;
|
|
start -= length;
|
|
}
|
|
|
|
unsigned int startIndex = intervalMerge(marking, start, false);
|
|
unsigned int endIndex = intervalMerge(marking, start + length, false);
|
|
|
|
while (startIndex < endIndex) {
|
|
marking[startIndex].second = value;
|
|
++startIndex;
|
|
}
|
|
}
|
|
|
|
void DrawUtil::intervalMarkCircular(std::vector<std::pair<double, bool>>& marking, double start,
|
|
double length, bool value)
|
|
{
|
|
if (length == 0.0) {
|
|
return;
|
|
}
|
|
if (length < 0.0) {
|
|
length = -length;
|
|
start -= length;
|
|
}
|
|
if (length > M_2PI) {
|
|
length = M_2PI;
|
|
}
|
|
|
|
angleNormalize(start);
|
|
|
|
double end = start + length;
|
|
if (end > M_PI) {
|
|
end -= M_2PI;
|
|
}
|
|
|
|
// Just make sure the point is stored, its index is read last
|
|
intervalMerge(marking, end, true);
|
|
unsigned int startIndex = intervalMerge(marking, start, true);
|
|
unsigned int endIndex = intervalMerge(marking, end, true);
|
|
|
|
do {
|
|
marking[startIndex].second = value;
|
|
++startIndex;
|
|
startIndex %= marking.size();
|
|
} while (startIndex != endIndex);
|
|
}
|
|
|
|
// Supplementary 2D analytic geometry functions
|
|
//=============================================
|
|
|
|
int DrawUtil::findRootForValue(double Ax2, double Bxy, double Cy2, double Dx, double Ey, double F,
|
|
double value, bool findX, double roots[])
|
|
{
|
|
double qA = 0.0;
|
|
double qB = 0.0;
|
|
double qC = 0.0;
|
|
|
|
if (findX) {
|
|
qA = Ax2;
|
|
qB = Bxy * value + Dx;
|
|
qC = Cy2 * value * value + Ey * value + F;
|
|
} else {
|
|
qA = Cy2;
|
|
qB = Bxy * value + Ey;
|
|
qC = Ax2 * value * value + Dx * value + F;
|
|
}
|
|
|
|
if (fabs(qA) < Precision::Confusion()) {
|
|
// No quadratic coefficient - the equation is linear
|
|
if (fabs(qB) < Precision::Confusion()) {
|
|
// Not even linear coefficient - test for zero
|
|
if (fabs(qC) > Precision::Confusion()) {
|
|
// This equation has no solution
|
|
return 0;
|
|
} else {
|
|
// Signal infinite number of solutions by returning 2, but do not touch root variables
|
|
return 2;
|
|
}
|
|
} else {
|
|
roots[0] = -qC / qB;
|
|
return 1;
|
|
}
|
|
} else {
|
|
double qD = sqr(qB) - 4.0 * qA * qC;
|
|
if (qD < -Precision::Confusion()) {
|
|
// Negative discriminant => no real roots
|
|
return 0;
|
|
} else if (qD > +Precision::Confusion()) {
|
|
// Two distinctive roots
|
|
roots[0] = (-qB + sqrt(qD)) * 0.5 / qA;
|
|
roots[1] = (-qB - sqrt(qD)) * 0.5 / qA;
|
|
return 2;
|
|
} else {
|
|
// Double root
|
|
roots[0] = -qB * 0.5 / qA;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DrawUtil::mergeBoundedPoint(const Base::Vector2d& point, const Base::BoundBox2d& boundary,
|
|
std::vector<Base::Vector2d>& storage)
|
|
{
|
|
if (!boundary.Contains(point, Precision::Confusion())) {
|
|
return false;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < storage.size(); ++i) {
|
|
if (point.IsEqual(storage[i], Precision::Confusion())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
storage.push_back(point);
|
|
return true;
|
|
}
|
|
|
|
void DrawUtil::findConicRectangleIntersections(double conicAx2, double conicBxy, double conicCy2,
|
|
double conicDx, double conicEy, double conicF,
|
|
const Base::BoundBox2d& rectangle,
|
|
std::vector<Base::Vector2d>& intersections)
|
|
{
|
|
double roots[2];
|
|
int rootCount;
|
|
|
|
// Find intersections with rectangle left side line
|
|
roots[0] = rectangle.MinY;
|
|
roots[1] = rectangle.MaxY;
|
|
rootCount = findRootForValue(
|
|
conicAx2, conicBxy, conicCy2, conicDx, conicEy, conicF, rectangle.MinX, false, roots);
|
|
if (rootCount > 0) {
|
|
mergeBoundedPoint(Base::Vector2d(rectangle.MinX, roots[0]), rectangle, intersections);
|
|
}
|
|
if (rootCount > 1) {
|
|
mergeBoundedPoint(Base::Vector2d(rectangle.MinX, roots[1]), rectangle, intersections);
|
|
}
|
|
|
|
// Find intersections with rectangle right side line
|
|
roots[0] = rectangle.MinY;
|
|
roots[1] = rectangle.MaxY;
|
|
rootCount = findRootForValue(
|
|
conicAx2, conicBxy, conicCy2, conicDx, conicEy, conicF, rectangle.MaxX, false, roots);
|
|
if (rootCount > 0) {
|
|
mergeBoundedPoint(Base::Vector2d(rectangle.MaxX, roots[0]), rectangle, intersections);
|
|
}
|
|
if (rootCount > 1) {
|
|
mergeBoundedPoint(Base::Vector2d(rectangle.MaxX, roots[1]), rectangle, intersections);
|
|
}
|
|
|
|
// Find intersections with rectangle top side line
|
|
roots[0] = rectangle.MinX;
|
|
roots[1] = rectangle.MaxX;
|
|
rootCount = findRootForValue(
|
|
conicAx2, conicBxy, conicCy2, conicDx, conicEy, conicF, rectangle.MinY, true, roots);
|
|
if (rootCount > 0) {
|
|
mergeBoundedPoint(Base::Vector2d(roots[0], rectangle.MinY), rectangle, intersections);
|
|
}
|
|
if (rootCount > 1) {
|
|
mergeBoundedPoint(Base::Vector2d(roots[1], rectangle.MinY), rectangle, intersections);
|
|
}
|
|
|
|
// Find intersections with rectangle top side line
|
|
roots[0] = rectangle.MinX;
|
|
roots[1] = rectangle.MaxX;
|
|
rootCount = findRootForValue(
|
|
conicAx2, conicBxy, conicCy2, conicDx, conicEy, conicF, rectangle.MaxY, true, roots);
|
|
if (rootCount > 0) {
|
|
mergeBoundedPoint(Base::Vector2d(roots[0], rectangle.MaxY), rectangle, intersections);
|
|
}
|
|
if (rootCount > 1) {
|
|
mergeBoundedPoint(Base::Vector2d(roots[1], rectangle.MaxY), rectangle, intersections);
|
|
}
|
|
}
|
|
|
|
void DrawUtil::findLineRectangleIntersections(const Base::Vector2d& linePoint, double lineAngle,
|
|
const Base::BoundBox2d& rectangle,
|
|
std::vector<Base::Vector2d>& intersections)
|
|
{
|
|
Base::Vector2d lineDirection(Base::Vector2d::FromPolar(1.0, lineAngle));
|
|
findConicRectangleIntersections(0.0,
|
|
0.0,
|
|
0.0,
|
|
+lineDirection.y,
|
|
-lineDirection.x,
|
|
lineDirection.x * linePoint.y - lineDirection.y * linePoint.x,
|
|
rectangle,
|
|
intersections);
|
|
}
|
|
|
|
void DrawUtil::findCircleRectangleIntersections(const Base::Vector2d& circleCenter,
|
|
double circleRadius,
|
|
const Base::BoundBox2d& rectangle,
|
|
std::vector<Base::Vector2d>& intersections)
|
|
{
|
|
findConicRectangleIntersections(1.0,
|
|
0.0,
|
|
1.0,
|
|
-2.0 * circleCenter.x,
|
|
-2.0 * circleCenter.y,
|
|
sqr(circleCenter.x) + sqr(circleCenter.y) - sqr(circleRadius),
|
|
rectangle,
|
|
intersections);
|
|
}
|
|
|
|
void DrawUtil::findLineSegmentRectangleIntersections(const Base::Vector2d& linePoint,
|
|
double lineAngle, double segmentBasePosition,
|
|
double segmentLength,
|
|
const Base::BoundBox2d& rectangle,
|
|
std::vector<Base::Vector2d>& intersections)
|
|
{
|
|
findLineRectangleIntersections(linePoint, lineAngle, rectangle, intersections);
|
|
|
|
if (segmentLength < 0.0) {
|
|
segmentLength = -segmentLength;
|
|
segmentBasePosition -= segmentLength;
|
|
}
|
|
|
|
// Dispose the points on rectangle but not within the line segment boundaries
|
|
Base::Vector2d segmentDirection(Base::Vector2d::FromPolar(1.0, lineAngle));
|
|
for (unsigned int i = 0; i < intersections.size();) {
|
|
double pointPosition = segmentDirection * (intersections[i] - linePoint);
|
|
|
|
if (pointPosition < segmentBasePosition - Precision::Confusion()
|
|
|| pointPosition > segmentBasePosition + segmentLength + Precision::Confusion()) {
|
|
intersections.erase(intersections.begin() + i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Try to add the line segment end points
|
|
mergeBoundedPoint(linePoint + segmentBasePosition * segmentDirection, rectangle, intersections);
|
|
mergeBoundedPoint(linePoint + (segmentBasePosition + segmentLength) * segmentDirection,
|
|
rectangle,
|
|
intersections);
|
|
}
|
|
|
|
void DrawUtil::findCircularArcRectangleIntersections(const Base::Vector2d& circleCenter,
|
|
double circleRadius, double arcBaseAngle,
|
|
double arcRotation,
|
|
const Base::BoundBox2d& rectangle,
|
|
std::vector<Base::Vector2d>& intersections)
|
|
{
|
|
findCircleRectangleIntersections(circleCenter, circleRadius, rectangle, intersections);
|
|
|
|
if (arcRotation < 0.0) {
|
|
arcRotation = -arcRotation;
|
|
arcBaseAngle -= arcRotation;
|
|
if (arcBaseAngle <= -M_PI) {
|
|
arcBaseAngle += M_2PI;
|
|
}
|
|
}
|
|
|
|
// Dispose the points on rectangle but not within the circular arc boundaries
|
|
for (unsigned int i = 0; i < intersections.size();) {
|
|
double pointAngle = (intersections[i] - circleCenter).Angle();
|
|
if (pointAngle < arcBaseAngle - Precision::Confusion()) {
|
|
pointAngle += M_2PI;
|
|
}
|
|
|
|
if (pointAngle > arcBaseAngle + arcRotation + Precision::Confusion()) {
|
|
intersections.erase(intersections.begin() + i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Try to add the circular arc end points
|
|
mergeBoundedPoint(circleCenter + Base::Vector2d::FromPolar(circleRadius, arcBaseAngle),
|
|
rectangle,
|
|
intersections);
|
|
mergeBoundedPoint(circleCenter
|
|
+ Base::Vector2d::FromPolar(circleRadius, arcBaseAngle + arcRotation),
|
|
rectangle,
|
|
intersections);
|
|
}
|
|
|
|
//copy whole text file from inSpec to outSpec
|
|
//create empty outSpec file if inSpec
|
|
void DrawUtil::copyFile(std::string inSpec, std::string outSpec)
|
|
{
|
|
// Base::Console().Message("DU::copyFile(%s, %s)\n", inSpec.c_str(), outSpec.c_str());
|
|
if (inSpec.empty()) {
|
|
// create an empty file
|
|
Base::FileInfo fi(outSpec);
|
|
Base::ofstream output(fi);
|
|
return;
|
|
}
|
|
Base::FileInfo fi(inSpec);
|
|
if (!fi.isReadable()) {
|
|
return;
|
|
}
|
|
bool rc = fi.copyTo(outSpec.c_str());
|
|
if (!rc) {
|
|
Base::Console().Message(
|
|
"DU::copyFile - failed - in: %s out:%s\n", inSpec.c_str(), outSpec.c_str());
|
|
}
|
|
}
|
|
|
|
//! static method that provides a translated std::string for objects that are not derived from DrawView
|
|
std::string DrawUtil::translateArbitrary(std::string context, std::string baseName, std::string uniqueName)
|
|
{
|
|
std::string suffix("");
|
|
if (uniqueName.length() > baseName.length()) {
|
|
suffix = uniqueName.substr(baseName.length(), uniqueName.length() - baseName.length());
|
|
}
|
|
QString qTranslated = qApp->translate(context.c_str(), baseName.c_str());
|
|
return qTranslated.toStdString() + suffix;
|
|
}
|
|
|
|
// true if owner->element is a cosmetic vertex
|
|
bool DrawUtil::isCosmeticVertex(App::DocumentObject* owner, std::string element)
|
|
{
|
|
auto ownerView = static_cast<TechDraw::DrawViewPart*>(owner);
|
|
auto vertexIndex = DrawUtil::getIndexFromName(element);
|
|
auto vertex = ownerView->getProjVertexByIndex(vertexIndex);
|
|
if (vertex) {
|
|
return vertex->getCosmetic();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// true if owner->element is a cosmetic edge
|
|
bool DrawUtil::isCosmeticEdge(App::DocumentObject* owner, std::string element)
|
|
{
|
|
auto ownerView = static_cast<TechDraw::DrawViewPart*>(owner);
|
|
auto edge = ownerView->getEdge(element);
|
|
if (edge && edge->source() == SourceType::COSMETICEDGE && edge->getCosmetic()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// true if owner->element is a center line
|
|
bool DrawUtil::isCenterLine(App::DocumentObject* owner, std::string element)
|
|
{
|
|
auto ownerView = static_cast<TechDraw::DrawViewPart*>(owner);
|
|
auto edge = ownerView->getEdge(element);
|
|
if (edge && edge->source() == SourceType::CENTERLINE && edge->getCosmetic()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//! convert a filespec (string) containing '\' to only use '/'.
|
|
//! prevents situation where '\' is interpreted as an escape of the next character in Python
|
|
//! commands.
|
|
std::string DrawUtil::cleanFilespecBackslash(const std::string& filespec)
|
|
{
|
|
std::string forwardSlash{"/"};
|
|
boost::regex rxBackslash("\\\\"); //this rx really means match to a single '\'
|
|
std::string noBackslash = boost::regex_replace(filespec, rxBackslash, forwardSlash);
|
|
return noBackslash;
|
|
}
|
|
|
|
|
|
//============================
|
|
// various debugging routines.
|
|
void DrawUtil::dumpVertexes(const char* text, const TopoDS_Shape& s)
|
|
{
|
|
Base::Console().Message("DUMP - %s\n", text);
|
|
TopExp_Explorer expl(s, TopAbs_VERTEX);
|
|
for (int i = 1; expl.More(); expl.Next(), i++) {
|
|
const TopoDS_Vertex& v = TopoDS::Vertex(expl.Current());
|
|
gp_Pnt pnt = BRep_Tool::Pnt(v);
|
|
Base::Console().Message("v%d: (%.3f, %.3f, %.3f)\n", i, pnt.X(), pnt.Y(), pnt.Z());
|
|
}
|
|
}
|
|
|
|
void DrawUtil::countFaces(const char* text, const TopoDS_Shape& s)
|
|
{
|
|
TopTools_IndexedMapOfShape mapOfFaces;
|
|
TopExp::MapShapes(s, TopAbs_FACE, mapOfFaces);
|
|
int num = mapOfFaces.Extent();
|
|
Base::Console().Message("COUNT - %s has %d Faces\n", text, num);
|
|
}
|
|
|
|
//count # of unique Wires in shape.
|
|
void DrawUtil::countWires(const char* text, const TopoDS_Shape& s)
|
|
{
|
|
TopTools_IndexedMapOfShape mapOfWires;
|
|
TopExp::MapShapes(s, TopAbs_WIRE, mapOfWires);
|
|
int num = mapOfWires.Extent();
|
|
Base::Console().Message("COUNT - %s has %d wires\n", text, num);
|
|
}
|
|
|
|
void DrawUtil::countEdges(const char* text, const TopoDS_Shape& s)
|
|
{
|
|
TopTools_IndexedMapOfShape mapOfEdges;
|
|
TopExp::MapShapes(s, TopAbs_EDGE, mapOfEdges);
|
|
int num = mapOfEdges.Extent();
|
|
Base::Console().Message("COUNT - %s has %d edges\n", text, num);
|
|
}
|
|
|
|
void DrawUtil::dumpEdges(const char* text, const TopoDS_Shape& s)
|
|
{
|
|
Base::Console().Message("DUMP - %s\n", text);
|
|
TopExp_Explorer expl(s, TopAbs_EDGE);
|
|
for (int i = 1; expl.More(); expl.Next(), i++) {
|
|
const TopoDS_Edge& e = TopoDS::Edge(expl.Current());
|
|
dumpEdge("dumpEdges", i, e);
|
|
}
|
|
}
|
|
|
|
void DrawUtil::dump1Vertex(const char* text, const TopoDS_Vertex& v)
|
|
{
|
|
// Base::Console().Message("DUMP - dump1Vertex - %s\n",text);
|
|
gp_Pnt pnt = BRep_Tool::Pnt(v);
|
|
Base::Console().Message("%s: (%.3f, %.3f, %.3f)\n", text, pnt.X(), pnt.Y(), pnt.Z());
|
|
}
|
|
|
|
void DrawUtil::dumpEdge(const char* label, int i, TopoDS_Edge e)
|
|
{
|
|
BRepAdaptor_Curve adapt(e);
|
|
double start = BRepLProp_CurveTool::FirstParameter(adapt);
|
|
double end = BRepLProp_CurveTool::LastParameter(adapt);
|
|
BRepLProp_CLProps propStart(adapt, start, 0, Precision::Confusion());
|
|
const gp_Pnt& vStart = propStart.Value();
|
|
BRepLProp_CLProps propEnd(adapt, end, 0, Precision::Confusion());
|
|
const gp_Pnt& vEnd = propEnd.Value();
|
|
//Base::Console().Message("%s edge:%d start:(%.3f, %.3f, %.3f)/%0.3f end:(%.2f, %.3f, %.3f)/%.3f\n", label, i,
|
|
// vStart.X(), vStart.Y(), vStart.Z(), start, vEnd.X(), vEnd.Y(), vEnd.Z(), end);
|
|
Base::Console().Message(
|
|
"%s edge:%d start:(%.3f, %.3f, %.3f) end:(%.2f, %.3f, %.3f) Orient: %d\n",
|
|
label,
|
|
i,
|
|
vStart.X(),
|
|
vStart.Y(),
|
|
vStart.Z(),
|
|
vEnd.X(),
|
|
vEnd.Y(),
|
|
vEnd.Z(),
|
|
static_cast<int>(e.Orientation()));
|
|
double edgeLength = GCPnts_AbscissaPoint::Length(adapt, Precision::Confusion());
|
|
Base::Console().Message(">>>>>>> length: %.3f distance: %.3f ratio: %.3f type: %d\n",
|
|
edgeLength,
|
|
vStart.Distance(vEnd),
|
|
edgeLength / vStart.Distance(vEnd),
|
|
static_cast<int>(adapt.GetType()));
|
|
}
|
|
|
|
const char* DrawUtil::printBool(bool b)
|
|
{
|
|
return (b ? "True" : "False");
|
|
}
|
|
|
|
QString DrawUtil::qbaToDebug(const QByteArray& line)
|
|
{
|
|
QString s;
|
|
uchar c;
|
|
|
|
for (int i = 0; i < line.size(); i++) {
|
|
c = line[i];
|
|
if ((c >= 0x20) && (c <= 126)) {
|
|
s.append(QChar::fromLatin1(c));
|
|
} else {
|
|
s.append(QStringLiteral("<%1>").arg(c, 2, 16, QChar::fromLatin1('0')));
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void DrawUtil::dumpCS(const char* text, const gp_Ax2& CS)
|
|
{
|
|
gp_Dir baseAxis = CS.Direction();
|
|
gp_Dir baseX = CS.XDirection();
|
|
gp_Dir baseY = CS.YDirection();
|
|
gp_Pnt baseOrg = CS.Location();
|
|
Base::Console().Message("DU::dumpCS - %s Loc: %s Axis: %s X: %s Y: %s\n",
|
|
text,
|
|
DrawUtil::formatVector(baseOrg).c_str(),
|
|
DrawUtil::formatVector(baseAxis).c_str(),
|
|
DrawUtil::formatVector(baseX).c_str(),
|
|
DrawUtil::formatVector(baseY).c_str());
|
|
}
|
|
|
|
void DrawUtil::dumpCS3(const char* text, const gp_Ax3& CS)
|
|
{
|
|
gp_Dir baseAxis = CS.Direction();
|
|
gp_Dir baseX = CS.XDirection();
|
|
gp_Dir baseY = CS.YDirection();
|
|
gp_Pnt baseOrg = CS.Location();
|
|
Base::Console().Message("DU::dumpCS3 - %s Loc: %s Axis: %s X: %s Y: %s\n",
|
|
text,
|
|
DrawUtil::formatVector(baseOrg).c_str(),
|
|
DrawUtil::formatVector(baseAxis).c_str(),
|
|
DrawUtil::formatVector(baseX).c_str(),
|
|
DrawUtil::formatVector(baseY).c_str());
|
|
}
|