// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2008 Jürgen Riegel * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Constraint.h" #include "ConstraintPy.h" using namespace Sketcher; using namespace Base; TYPESYSTEM_SOURCE(Sketcher::Constraint, Base::Persistence) Constraint::Constraint() { // Initialize a random number generator, to avoid Valgrind false positives. // The random number generator is not threadsafe so we guard it. See // https://www.boost.org/doc/libs/1_62_0/libs/uuid/uuid.html#Design%20notes static boost::mt19937 ran; static bool seeded = false; static boost::mutex random_number_mutex; boost::lock_guard guard(random_number_mutex); if (!seeded) { ran.seed(QDateTime::currentMSecsSinceEpoch() & 0xffffffff); seeded = true; } static boost::uuids::basic_random_generator gen(&ran); tag = gen(); } Constraint* Constraint::clone() const { return new Constraint(*this); } Constraint* Constraint::copy() const { Constraint* temp = new Constraint(); temp->Value = this->Value; temp->Type = this->Type; temp->AlignmentType = this->AlignmentType; temp->Name = this->Name; temp->LabelDistance = this->LabelDistance; temp->LabelPosition = this->LabelPosition; temp->isDriving = this->isDriving; temp->InternalAlignmentIndex = this->InternalAlignmentIndex; temp->isInVirtualSpace = this->isInVirtualSpace; temp->isVisible = this->isVisible; temp->isActive = this->isActive; temp->elements = this->elements; // Do not copy tag, otherwise it is considered a clone, and a "rename" by the expression engine. #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS temp->First = this->First; temp->FirstPos = this->FirstPos; temp->Second = this->Second; temp->SecondPos = this->SecondPos; temp->Third = this->Third; temp->ThirdPos = this->ThirdPos; #endif return temp; } PyObject* Constraint::getPyObject() { return new ConstraintPy(new Constraint(*this)); } Quantity Constraint::getPresentationValue() const { Quantity quantity; switch (Type) { case Distance: case Radius: case Diameter: case DistanceX: case DistanceY: quantity.setValue(Value); quantity.setUnit(Unit::Length); break; case Angle: quantity.setValue(toDegrees(Value)); quantity.setUnit(Unit::Angle); break; case SnellsLaw: case Weight: quantity.setValue(Value); break; default: quantity.setValue(Value); break; } QuantityFormat format = quantity.getFormat(); format.option = QuantityFormat::None; format.format = QuantityFormat::Default; format.setPrecision(6); // QString's default quantity.setFormat(format); return quantity; } unsigned int Constraint::getMemSize() const { return 0; } void Constraint::Save(Writer& writer) const { std::string encodeName = encodeAttribute(Name); writer.Stream() << writer.ind() << "Type == InternalAlignment) { writer.Stream() << "InternalAlignmentType=\"" << (int)AlignmentType << "\" " << "InternalAlignmentIndex=\"" << InternalAlignmentIndex << "\" "; } writer.Stream() << "Value=\"" << Value << "\" " << "LabelDistance=\"" << LabelDistance << "\" " << "LabelPosition=\"" << LabelPosition << "\" " << "IsDriving=\"" << (int)isDriving << "\" " << "IsInVirtualSpace=\"" << (int)isInVirtualSpace << "\" " << "IsVisible=\"" << (int)isVisible << "\" " << "IsActive=\"" << (int)isActive << "\" "; // Save elements { // Ensure backwards compatibility with old versions writer.Stream() << "First=\"" << getElement(0).GeoId << "\" " << "FirstPos=\"" << getElement(0).posIdAsInt() << "\" " << "Second=\"" << getElement(1).GeoId << "\" " << "SecondPos=\"" << getElement(1).posIdAsInt() << "\" " << "Third=\"" << getElement(2).GeoId << "\" " << "ThirdPos=\"" << getElement(2).posIdAsInt() << "\" "; #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS auto elements = std::views::iota(size_t {0}, this->elements.size()) | std::views::transform([&](size_t i) { return getElement(i); }); #endif auto geoIds = elements | std::views::transform([](const GeoElementId& e) { return e.GeoId; }); auto posIds = elements | std::views::transform([](const GeoElementId& e) { return e.posIdAsInt(); }); const std::string ids = fmt::format("{}", fmt::join(geoIds, " ")); const std::string positions = fmt::format("{}", fmt::join(posIds, " ")); writer.Stream() << "ElementIds=\"" << ids << "\" " << "ElementPositions=\"" << positions << "\" "; } writer.Stream() << "/>\n"; } void Constraint::Restore(XMLReader& reader) { reader.readElement("Constrain"); Name = reader.getAttribute("Name"); Type = reader.getAttribute("Type"); Value = reader.getAttribute("Value"); if (this->Type == InternalAlignment) { AlignmentType = reader.getAttribute("InternalAlignmentType"); if (reader.hasAttribute("InternalAlignmentIndex")) { InternalAlignmentIndex = reader.getAttribute("InternalAlignmentIndex"); } } else { AlignmentType = Undef; } // Read the distance a constraint label has been moved if (reader.hasAttribute("LabelDistance")) { LabelDistance = (float)reader.getAttribute("LabelDistance"); } if (reader.hasAttribute("LabelPosition")) { LabelPosition = (float)reader.getAttribute("LabelPosition"); } if (reader.hasAttribute("IsDriving")) { isDriving = reader.getAttribute("IsDriving"); } if (reader.hasAttribute("IsInVirtualSpace")) { isInVirtualSpace = reader.getAttribute("IsInVirtualSpace"); } if (reader.hasAttribute("IsVisible")) { isVisible = reader.getAttribute("IsVisible"); } if (reader.hasAttribute("IsActive")) { isActive = reader.getAttribute("IsActive"); } if (reader.hasAttribute("ElementIds") && reader.hasAttribute("ElementPositions")) { auto splitAndClean = [](std::string_view input) { const char delimiter = ' '; auto tokens = input | std::views::split(delimiter) | std::views::transform([](auto&& subrange) { // workaround due to lack of std::ranges::to in c++20 std::string token; auto size = std::ranges::distance(subrange); token.reserve(size); for (char c : subrange) { token.push_back(c); } return token; }) | std::views::filter([](const std::string& s) { return !s.empty(); }); return std::vector(tokens.begin(), tokens.end()); }; const std::string elementIds = reader.getAttribute("ElementIds"); const std::string elementPositions = reader.getAttribute("ElementPositions"); const auto ids = splitAndClean(elementIds); const auto positions = splitAndClean(elementPositions); if (ids.size() != positions.size()) { throw Base::ParserError( fmt::format( "ElementIds and ElementPositions do not match in " "size. Got {} ids and {} positions.", ids.size(), positions.size() ) ); } elements.clear(); for (size_t i = 0; i < std::min(ids.size(), positions.size()); ++i) { const int geoId {std::stoi(ids[i])}; const PointPos pos {static_cast(std::stoi(positions[i]))}; addElement(GeoElementId(geoId, pos)); } } // Ensure we have at least 3 elements while (getElementsSize() < 3) { addElement(GeoElementId(GeoEnum::GeoUndef, PointPos::none)); } // Load deprecated First, Second, Third elements // These take precedence over the new elements // Even though these are deprecated, we still need to read them // for compatibility with old files. { constexpr std::array names = {"First", "Second", "Third"}; constexpr std::array posNames = {"FirstPos", "SecondPos", "ThirdPos"}; static_assert(names.size() == posNames.size()); for (size_t i = 0; i < names.size(); ++i) { if (reader.hasAttribute(names[i])) { const int geoId {reader.getAttribute(names[i])}; const PointPos pos {reader.getAttribute(posNames[i])}; setElement(i, GeoElementId(geoId, pos)); } } } } void Constraint::substituteIndex(int fromGeoId, int toGeoId) { #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS for (size_t i = 0; i < elements.size(); ++i) { const GeoElementId element = getElement(i); if (element.GeoId == fromGeoId) { setElement(i, GeoElementId(toGeoId, element.Pos)); } } #else for (auto& element : elements) { if (element.GeoId == fromGeoId) { element = GeoElementId(toGeoId, element.Pos); } } #endif } void Constraint::substituteIndexAndPos(int fromGeoId, PointPos fromPosId, int toGeoId, PointPos toPosId) { const GeoElementId from {fromGeoId, fromPosId}; #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS for (size_t i = 0; i < elements.size(); ++i) { const GeoElementId element = getElement(i); if (element == from) { setElement(i, GeoElementId(toGeoId, toPosId)); } } #else for (auto& element : elements) { if (element == from) { element = GeoElementId(toGeoId, toPosId); } } #endif } std::string Constraint::typeToString(ConstraintType type) { return type2str[type]; } std::string Constraint::internalAlignmentTypeToString(InternalAlignmentType alignment) { return internalAlignmentType2str[alignment]; } bool Constraint::involvesGeoId(int geoId) const { #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS auto elements = std::views::iota(size_t {0}, this->elements.size()) | std::views::transform([&](size_t i) { return getElement(i); }); #endif return std::ranges::any_of(elements, [geoId](const auto& element) { return element.GeoId == geoId; }); } /// utility function to check if (`geoId`, `posId`) is one of the points/curves bool Constraint::involvesGeoIdAndPosId(int geoId, PointPos posId) const { #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS auto elements = std::views::iota(size_t {0}, this->elements.size()) | std::views::transform([&](size_t i) { return getElement(i); }); #endif return std::ranges::find(elements, GeoElementId(geoId, posId)) != elements.end(); } GeoElementId Constraint::getElement(size_t index) const { if (index >= elements.size()) { throw Base::IndexError("Constraint::getElement index out of range"); } #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS if (index < 3) { switch (index) { case 0: return GeoElementId(First, FirstPos); case 1: return GeoElementId(Second, SecondPos); case 2: return GeoElementId(Third, ThirdPos); } } #endif return elements[index]; } void Constraint::setElement(size_t index, GeoElementId element) { if (index >= elements.size()) { throw Base::IndexError("Constraint::getElement index out of range"); } elements[index] = element; #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS if (index < 3) { switch (index) { case 0: First = element.GeoId; FirstPos = element.Pos; break; case 1: Second = element.GeoId; SecondPos = element.Pos; break; case 2: Third = element.GeoId; ThirdPos = element.Pos; break; } } #endif } size_t Constraint::getElementsSize() const { return elements.size(); } void Constraint::addElement(GeoElementId element) { #if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS int i = elements.size(); elements.resize(i + 1); setElement(i, element); #else elements.push_back(element); #endif }