[TD]Refactor Dimensions for 3d upgrade
- separate validation, geometry and reference handling into individual files - improve 3d reference geometry handling - eliminate duplicate dim creation code - add Dimension reference repair dialog - Refactor formatting out of DrawViewDimension - move dimension repaint control to ViewProvider
This commit is contained in:
408
src/Mod/TechDraw/App/DimensionFormatter.cpp
Normal file
408
src/Mod/TechDraw/App/DimensionFormatter.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2022 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"
|
||||
#include <QRegularExpression>
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/UnitsApi.h>
|
||||
|
||||
#include "Preferences.h"
|
||||
#include "DrawViewDimension.h"
|
||||
#include "DimensionFormatter.h"
|
||||
|
||||
using namespace TechDraw;
|
||||
|
||||
bool DimensionFormatter::isMultiValueSchema() const
|
||||
{
|
||||
bool angularMeasure = (m_dimension->Type.isValue("Angle") ||
|
||||
m_dimension->Type.isValue("Angle3Pt"));
|
||||
|
||||
if (Base::UnitsApi::isMultiUnitAngle() &&
|
||||
angularMeasure) {
|
||||
return true;
|
||||
} else if (Base::UnitsApi::isMultiUnitLength() &&
|
||||
!angularMeasure) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//partial = 0 return the unaltered user string from the Units subsystem
|
||||
//partial = 1 return value formatted according to the format spec and preferences for
|
||||
// useAltDecimals and showUnits
|
||||
//partial = 2 return only the unit of measure
|
||||
std::string DimensionFormatter::formatValue(qreal value,
|
||||
QString qFormatSpec,
|
||||
int partial,
|
||||
bool isDim)
|
||||
{
|
||||
bool angularMeasure = false;
|
||||
QLocale loc;
|
||||
|
||||
Base::Quantity asQuantity;
|
||||
asQuantity.setValue(value);
|
||||
if ( (m_dimension->Type.isValue("Angle")) ||
|
||||
(m_dimension->Type.isValue("Angle3Pt")) ) {
|
||||
angularMeasure = true;
|
||||
asQuantity.setUnit(Base::Unit::Angle);
|
||||
} else {
|
||||
asQuantity.setUnit(Base::Unit::Length);
|
||||
}
|
||||
|
||||
QString qUserString = asQuantity.getUserString(); // this handles mm to inch/km/parsec etc
|
||||
// and decimal positions but won't give more than
|
||||
// Global_Decimals precision
|
||||
|
||||
//get formatSpec prefix/suffix/specifier
|
||||
QStringList qsl = getPrefixSuffixSpec(qFormatSpec);
|
||||
QString formatPrefix = qsl[0]; //FormatSpec prefix
|
||||
QString formatSuffix = qsl[1]; //FormatSpec suffix
|
||||
QString formatSpecifier = qsl[2]; //FormatSpec specifier
|
||||
|
||||
QString qMultiValueStr;
|
||||
QString qBasicUnit = Base::Tools::fromStdString(Base::UnitsApi::getBasicLengthUnit());
|
||||
|
||||
QString formattedValue;
|
||||
if (isMultiValueSchema() && partial == 0) {
|
||||
//handle multi value schemes (yd/ft/in, dms, etc). don't even try to use Alt Decimals or hide units
|
||||
qMultiValueStr = formatPrefix + qUserString + formatSuffix;
|
||||
return qMultiValueStr.toStdString();
|
||||
} else {
|
||||
//not multivalue schema
|
||||
if (formatSpecifier.isEmpty()) {
|
||||
Base::Console().Warning("Warning - no numeric format in Format Spec %s - %s\n",
|
||||
qPrintable(qFormatSpec), m_dimension->getNameInDocument());
|
||||
return Base::Tools::toStdString(qFormatSpec);
|
||||
}
|
||||
|
||||
// for older TD drawings the formatSpecifier "%g" was used, but the number of decimals was
|
||||
// neverheless limited. To keep old drawings, we limit the number of decimals too
|
||||
// if the TD preferences option to use the global decimal number is set
|
||||
// the formatSpecifier can have a prefix and/or suffix
|
||||
if (m_dimension->useDecimals() && formatSpecifier.contains(QString::fromLatin1("%g"), Qt::CaseInsensitive)) {
|
||||
int globalPrecision = Base::UnitsApi::getDecimals();
|
||||
// change formatSpecifier to e.g. "%.2f"
|
||||
QString newSpecifier = QString::fromStdString("%." + std::to_string(globalPrecision) + "f");
|
||||
formatSpecifier.replace(QString::fromLatin1("%g"), newSpecifier, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
// since we are not using a multiValueSchema, we know that angles are in '°' and for
|
||||
// lengths we can get the unit of measure from UnitsApi::getBasicLengthUnit.
|
||||
|
||||
// TODO: check the weird schemas (MKS, Imperial1)that report different UoM
|
||||
// for different values
|
||||
|
||||
// get value in the base unit with default decimals
|
||||
// for the conversion we use the same method as in DlgUnitsCalculator::valueChanged
|
||||
// get the conversion factor for the unit
|
||||
// the result is now just val / convertValue because val is always in the base unit
|
||||
// don't do this for angular values since they are not in the BaseLengthUnit
|
||||
double userVal;
|
||||
if (angularMeasure) {
|
||||
userVal = asQuantity.getValue();
|
||||
qBasicUnit = QString::fromUtf8("°");
|
||||
} else {
|
||||
double convertValue = Base::Quantity::parse(QString::fromLatin1("1") + qBasicUnit).getValue();
|
||||
userVal = asQuantity.getValue() / convertValue;
|
||||
}
|
||||
|
||||
if (isTooSmall(userVal, formatSpecifier)) {
|
||||
Base::Console().Warning("Dimension value is too small for format specifier: %s\n", qPrintable(formatSpecifier));
|
||||
}
|
||||
|
||||
formattedValue = formatValueToSpec(userVal, formatSpecifier);
|
||||
|
||||
// replace decimal sign if necessary
|
||||
QChar dp = QChar::fromLatin1('.');
|
||||
if (loc.decimalPoint() != dp) {
|
||||
formattedValue.replace(dp, loc.decimalPoint());
|
||||
}
|
||||
}
|
||||
|
||||
//formattedValue is now in formatSpec format with local decimal separator
|
||||
std::string formattedValueString = formattedValue.toStdString();
|
||||
if (partial == 0) { //prefix + unit subsystem string + suffix
|
||||
return Base::Tools::toStdString(formatPrefix) +
|
||||
Base::Tools::toStdString(qUserString) +
|
||||
Base::Tools::toStdString(formatSuffix);
|
||||
} else if (partial == 1) { // prefix number[unit] suffix
|
||||
if (angularMeasure) {
|
||||
//always insert unit after value
|
||||
return Base::Tools::toStdString(formatPrefix) +
|
||||
formattedValueString + "°" +
|
||||
Base::Tools::toStdString(formatSuffix);
|
||||
} else if (m_dimension->showUnits()){
|
||||
if (isDim && m_dimension->haveTolerance()) {
|
||||
//unit will be included in tolerance so don't repeat it here
|
||||
return Base::Tools::toStdString(formatPrefix) +
|
||||
formattedValueString +
|
||||
Base::Tools::toStdString(formatSuffix);
|
||||
} else {
|
||||
//no tolerance, so we need to include unit
|
||||
return Base::Tools::toStdString(formatPrefix) +
|
||||
formattedValueString + " " +
|
||||
Base::Tools::toStdString(qBasicUnit) +
|
||||
Base::Tools::toStdString(formatSuffix);
|
||||
}
|
||||
} else {
|
||||
//showUnits is false
|
||||
return Base::Tools::toStdString(formatPrefix) +
|
||||
formattedValueString +
|
||||
Base::Tools::toStdString(formatSuffix);
|
||||
}
|
||||
} else if (partial == 2) { // just the unit
|
||||
if (angularMeasure) {
|
||||
return Base::Tools::toStdString(qBasicUnit);
|
||||
} else if (m_dimension->showUnits()) {
|
||||
return Base::Tools::toStdString(qBasicUnit);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return formattedValueString;
|
||||
}
|
||||
|
||||
std::string DimensionFormatter::getFormattedToleranceValue(int partial)
|
||||
{
|
||||
QString FormatSpec = QString::fromUtf8(m_dimension->FormatSpecOverTolerance.getStrValue().data());
|
||||
QString ToleranceString;
|
||||
|
||||
if (m_dimension->ArbitraryTolerances.getValue())
|
||||
ToleranceString = FormatSpec;
|
||||
else
|
||||
ToleranceString = QString::fromUtf8(formatValue(m_dimension->OverTolerance.getValue(),
|
||||
FormatSpec,
|
||||
partial,
|
||||
false).c_str());
|
||||
|
||||
return ToleranceString.toStdString();
|
||||
}
|
||||
|
||||
//get over and under tolerances
|
||||
std::pair<std::string, std::string> DimensionFormatter::getFormattedToleranceValues(int partial)
|
||||
{
|
||||
QString underFormatSpec = QString::fromUtf8(m_dimension->FormatSpecUnderTolerance.getStrValue().data());
|
||||
QString overFormatSpec = QString::fromUtf8(m_dimension->FormatSpecOverTolerance.getStrValue().data());
|
||||
std::pair<std::string, std::string> tolerances;
|
||||
QString underTolerance, overTolerance;
|
||||
|
||||
if (m_dimension->ArbitraryTolerances.getValue()) {
|
||||
underTolerance = underFormatSpec;
|
||||
overTolerance = overFormatSpec;
|
||||
} else {
|
||||
if (DrawUtil::fpCompare(m_dimension->UnderTolerance.getValue(), 0.0)) {
|
||||
underTolerance = QString::fromUtf8(formatValue(m_dimension->UnderTolerance.getValue(),
|
||||
QString::fromUtf8("%.0f"),
|
||||
partial,
|
||||
false).c_str());
|
||||
}
|
||||
else {
|
||||
underTolerance = QString::fromUtf8(formatValue(m_dimension->UnderTolerance.getValue(),
|
||||
underFormatSpec,
|
||||
partial,
|
||||
false).c_str());
|
||||
}
|
||||
if (DrawUtil::fpCompare(m_dimension->OverTolerance.getValue(), 0.0)) {
|
||||
overTolerance = QString::fromUtf8(formatValue(m_dimension->OverTolerance.getValue(),
|
||||
QString::fromUtf8("%.0f"),
|
||||
partial,
|
||||
false).c_str());
|
||||
}
|
||||
else {
|
||||
overTolerance = QString::fromUtf8(formatValue(m_dimension->OverTolerance.getValue(),
|
||||
overFormatSpec,
|
||||
partial,
|
||||
false).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
tolerances.first = underTolerance.toStdString();
|
||||
tolerances.second = overTolerance.toStdString();
|
||||
|
||||
return tolerances;
|
||||
}
|
||||
|
||||
//partial = 2 unit only
|
||||
std::string DimensionFormatter::getFormattedDimensionValue(int partial)
|
||||
{
|
||||
QString qFormatSpec = QString::fromUtf8(m_dimension->FormatSpec.getStrValue().data());
|
||||
|
||||
if ( (m_dimension->Arbitrary.getValue() && !m_dimension->EqualTolerance.getValue())
|
||||
|| (m_dimension->Arbitrary.getValue() && m_dimension->TheoreticalExact.getValue()) ) {
|
||||
return m_dimension->FormatSpec.getStrValue();
|
||||
}
|
||||
|
||||
if (m_dimension->Arbitrary.getValue()) {
|
||||
return m_dimension->FormatSpec.getStrValue();
|
||||
}
|
||||
|
||||
// if there is an equal over-/undertolerance (so only 1 tolerance to show with +/-) and
|
||||
// not theoretically exact (which has no tolerance), and
|
||||
// tolerance has been specified, ie
|
||||
// (OverTolerance != 0.0 (so a tolerance has been specified) or
|
||||
// ArbitraryTolerances are specified)
|
||||
// concatenate the tolerance to dimension
|
||||
if (m_dimension->EqualTolerance.getValue() &&
|
||||
!m_dimension->TheoreticalExact.getValue() &&
|
||||
(!DrawUtil::fpCompare(m_dimension->OverTolerance.getValue(), 0.0) ||
|
||||
m_dimension->ArbitraryTolerances.getValue())) {
|
||||
QString labelText = QString::fromUtf8(formatValue(m_dimension->getDimValue(),
|
||||
qFormatSpec,
|
||||
1,
|
||||
true).c_str()); //just the number pref/spec[unit]/suf
|
||||
QString unitText = QString::fromUtf8(formatValue(m_dimension->getDimValue(),
|
||||
qFormatSpec,
|
||||
2,
|
||||
false).c_str()); //just the unit
|
||||
QString tolerance = QString::fromStdString(getFormattedToleranceValue(1).c_str());
|
||||
|
||||
// tolerance might start with a plus sign that we don't want, so cut it off
|
||||
// note plus sign is not at pos = 0!
|
||||
QRegularExpression plus(QString::fromUtf8("^\\s*\\+"));
|
||||
tolerance.remove(plus);
|
||||
|
||||
return (labelText +
|
||||
QString::fromUtf8(" \xC2\xB1 ") + // +/- symbol
|
||||
tolerance).toStdString();
|
||||
|
||||
if (partial == 2) {
|
||||
return unitText.toStdString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
//tolerance not specified, so just format dimension value?
|
||||
std::string formattedValue = formatValue(m_dimension->getDimValue(), qFormatSpec, partial, true);
|
||||
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
// format the value using the formatSpec. Also, handle the non-standard format-
|
||||
// specifier '%w', which has the following rules: works as %f, but no trailing zeros
|
||||
QString DimensionFormatter::formatValueToSpec(double value, QString formatSpecifier)
|
||||
{
|
||||
QString formattedValue;
|
||||
if (formatSpecifier.contains(QRegularExpression(QStringLiteral("%.*[wW]")))) {
|
||||
QString fs = formatSpecifier;
|
||||
fs.replace(QRegularExpression(QStringLiteral("%(.*)w")), QStringLiteral("%\\1f"));
|
||||
fs.replace(QRegularExpression(QStringLiteral("%(.*)W")), QStringLiteral("%\\1F"));
|
||||
formattedValue = QString::asprintf(Base::Tools::toStdString(fs).c_str(), value);
|
||||
// First, try to cut trailing zeros, if AFTER decimal dot there are nonzero numbers
|
||||
// Second, try to cut also decimal dot and zeros, if there are just zeros after it
|
||||
formattedValue.replace(QRegularExpression(QStringLiteral("([0-9][0-9]*\\.[0-9]*[1-9])00*$")), QStringLiteral("\\1"));
|
||||
formattedValue.replace(QRegularExpression(QStringLiteral("([0-9][0-9]*)\\.0*$")), QStringLiteral("\\1"));
|
||||
} else {
|
||||
formattedValue = QString::asprintf(Base::Tools::toStdString(formatSpecifier).c_str(), value);
|
||||
}
|
||||
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
QStringList DimensionFormatter::getPrefixSuffixSpec(QString fSpec)
|
||||
{
|
||||
QStringList result;
|
||||
//find the %x.y tag in FormatSpec
|
||||
QRegularExpression rxFormat(QStringLiteral("%[+-]?[0-9]*\\.*[0-9]*[aefgwAEFGW]")); //printf double format spec
|
||||
QRegularExpressionMatch rxMatch;
|
||||
int pos = fSpec.indexOf(rxFormat, 0, &rxMatch);
|
||||
if (pos != -1) {
|
||||
QString match = rxMatch.captured(0); //entire capture of rx
|
||||
QString formatPrefix = fSpec.left(pos);
|
||||
result.append(formatPrefix);
|
||||
QString formatSuffix = fSpec.right(fSpec.size() - pos - match.size());
|
||||
result.append(formatSuffix);
|
||||
result.append(match);
|
||||
} else { //printf format not found!
|
||||
Base::Console().Warning("Warning - no numeric format in formatSpec %s - %s\n",
|
||||
qPrintable(fSpec), m_dimension->getNameInDocument());
|
||||
result.append(QString());
|
||||
result.append(QString());
|
||||
result.append(fSpec);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string DimensionFormatter::getDefaultFormatSpec(bool isToleranceFormat) const
|
||||
{
|
||||
std::string prefFormat = Preferences::formatSpec();
|
||||
QString formatSpec;
|
||||
QString qPrefix;
|
||||
if (prefFormat.empty()) {
|
||||
QString format1 = Base::Tools::fromStdString("%.");
|
||||
QString format2 = Base::Tools::fromStdString("f");
|
||||
int precision;
|
||||
if (m_dimension->useDecimals()) {
|
||||
precision = Base::UnitsApi::getDecimals();
|
||||
} else {
|
||||
precision = Preferences::altDecimals();
|
||||
}
|
||||
QString formatPrecision = QString::number(precision);
|
||||
|
||||
std::string prefix = m_dimension->getPrefixForDimType();
|
||||
|
||||
if (!prefix.empty()) {
|
||||
qPrefix = QString::fromUtf8(prefix.data(), prefix.size());
|
||||
}
|
||||
|
||||
formatSpec = qPrefix + format1 + formatPrecision + format2;
|
||||
} else {
|
||||
|
||||
std::string prefix = m_dimension->getPrefixForDimType();
|
||||
qPrefix = QString::fromUtf8(prefix.data(), prefix.size());
|
||||
formatSpec = qPrefix + QString::fromStdString(prefFormat);
|
||||
|
||||
}
|
||||
|
||||
if (isToleranceFormat) {
|
||||
formatSpec.replace(QString::fromUtf8("%"), QString::fromUtf8("%+"));
|
||||
}
|
||||
|
||||
return Base::Tools::toStdString(formatSpec);
|
||||
}
|
||||
|
||||
//true if value is too small to display using formatSpec
|
||||
bool DimensionFormatter::isTooSmall(double value, QString formatSpec)
|
||||
{
|
||||
if (DU::fpCompare(value, 0.0)) {
|
||||
//zero values always fit, so it isn't too small
|
||||
return false;
|
||||
}
|
||||
|
||||
QRegularExpression rxFormat(QStringLiteral("%[+-]?[0-9]*\\.*([0-9]*)[aefgwAEFGW]")); //printf double format spec
|
||||
QRegularExpressionMatch rxMatch = rxFormat.match(formatSpec);
|
||||
if (rxMatch.hasMatch()) {
|
||||
QString decimalGroup = rxMatch.captured(1);
|
||||
int factor = decimalGroup.toInt();
|
||||
double minValue = pow(10.0, -factor);
|
||||
if (value < minValue) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
Base::Console().Warning("Failed to parse dimension format spec\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user