Skip to content

Commit 5d58ac7

Browse files
authored
Merge pull request FreeCAD#22088 from hyarion/feature/variable-constraint-points
Sketcher: Adds support for constraint with more than 3 points
2 parents 8093579 + 7150ae1 commit 5d58ac7

File tree

6 files changed

+844
-83
lines changed

6 files changed

+844
-83
lines changed

src/Mod/AddonManager

Submodule AddonManager updated 218 files

src/Mod/Sketcher/App/Constraint.cpp

Lines changed: 229 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,16 @@
2424
#ifndef _PreComp_
2525
#include <QDateTime>
2626
#include <boost/random.hpp>
27+
#include <algorithm>
2728
#include <cmath>
29+
#include <ranges>
30+
#include <stdexcept>
31+
#include <string>
32+
#include <vector>
2833
#endif
2934

35+
#include <fmt/ranges.h>
36+
3037
#include <Base/Reader.h>
3138
#include <Base/Tools.h>
3239
#include <Base/Writer.h>
@@ -44,21 +51,6 @@ using namespace Base;
4451
TYPESYSTEM_SOURCE(Sketcher::Constraint, Base::Persistence)
4552

4653
Constraint::Constraint()
47-
: Value(0.0)
48-
, Type(None)
49-
, AlignmentType(Undef)
50-
, First(GeoEnum::GeoUndef)
51-
, FirstPos(PointPos::none)
52-
, Second(GeoEnum::GeoUndef)
53-
, SecondPos(PointPos::none)
54-
, Third(GeoEnum::GeoUndef)
55-
, ThirdPos(PointPos::none)
56-
, LabelDistance(10.f)
57-
, LabelPosition(0.f)
58-
, isDriving(true)
59-
, InternalAlignmentIndex(-1)
60-
, isInVirtualSpace(false)
61-
, isActive(true)
6254
{
6355
// Initialize a random number generator, to avoid Valgrind false positives.
6456
// The random number generator is not threadsafe so we guard it. See
@@ -90,19 +82,24 @@ Constraint* Constraint::copy() const
9082
temp->Type = this->Type;
9183
temp->AlignmentType = this->AlignmentType;
9284
temp->Name = this->Name;
93-
temp->First = this->First;
94-
temp->FirstPos = this->FirstPos;
95-
temp->Second = this->Second;
96-
temp->SecondPos = this->SecondPos;
97-
temp->Third = this->Third;
98-
temp->ThirdPos = this->ThirdPos;
9985
temp->LabelDistance = this->LabelDistance;
10086
temp->LabelPosition = this->LabelPosition;
10187
temp->isDriving = this->isDriving;
10288
temp->InternalAlignmentIndex = this->InternalAlignmentIndex;
10389
temp->isInVirtualSpace = this->isInVirtualSpace;
10490
temp->isActive = this->isActive;
91+
temp->elements = this->elements;
10592
// Do not copy tag, otherwise it is considered a clone, and a "rename" by the expression engine.
93+
94+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
95+
temp->First = this->First;
96+
temp->FirstPos = this->FirstPos;
97+
temp->Second = this->Second;
98+
temp->SecondPos = this->SecondPos;
99+
temp->Third = this->Third;
100+
temp->ThirdPos = this->ThirdPos;
101+
#endif
102+
106103
return temp;
107104
}
108105

@@ -160,19 +157,42 @@ void Constraint::Save(Writer& writer) const
160157
<< "InternalAlignmentIndex=\"" << InternalAlignmentIndex << "\" ";
161158
}
162159
writer.Stream() << "Value=\"" << Value << "\" "
163-
<< "First=\"" << First << "\" "
164-
<< "FirstPos=\"" << (int)FirstPos << "\" "
165-
<< "Second=\"" << Second << "\" "
166-
<< "SecondPos=\"" << (int)SecondPos << "\" "
167-
<< "Third=\"" << Third << "\" "
168-
<< "ThirdPos=\"" << (int)ThirdPos << "\" "
169160
<< "LabelDistance=\"" << LabelDistance << "\" "
170161
<< "LabelPosition=\"" << LabelPosition << "\" "
171162
<< "IsDriving=\"" << (int)isDriving << "\" "
172163
<< "IsInVirtualSpace=\"" << (int)isInVirtualSpace << "\" "
173-
<< "IsActive=\"" << (int)isActive << "\" />"
164+
<< "IsActive=\"" << (int)isActive << "\" ";
165+
166+
// Save elements
167+
{
168+
// Ensure backwards compatibility with old versions
169+
writer.Stream() << "First=\"" << getElement(0).GeoId << "\" "
170+
<< "FirstPos=\"" << getElement(0).posIdAsInt() << "\" "
171+
<< "Second=\"" << getElement(1).GeoId << "\" "
172+
<< "SecondPos=\"" << getElement(1).posIdAsInt() << "\" "
173+
<< "Third=\"" << getElement(2).GeoId << "\" "
174+
<< "ThirdPos=\"" << getElement(2).posIdAsInt() << "\" ";
175+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
176+
auto elements = std::views::iota(size_t {0}, this->elements.size())
177+
| std::views::transform([&](size_t i) {
178+
return getElement(i);
179+
});
180+
#endif
181+
auto geoIds = elements | std::views::transform([](const GeoElementId& e) {
182+
return e.GeoId;
183+
});
184+
auto posIds = elements | std::views::transform([](const GeoElementId& e) {
185+
return e.posIdAsInt();
186+
});
187+
188+
const std::string ids = fmt::format("{}", fmt::join(geoIds, " "));
189+
const std::string positions = fmt::format("{}", fmt::join(posIds, " "));
190+
191+
writer.Stream() << "ElementIds=\"" << ids << "\" "
192+
<< "ElementPositions=\"" << positions << "\" ";
193+
}
174194

175-
<< std::endl;
195+
writer.Stream() << "/>\n";
176196
}
177197

178198
void Constraint::Restore(XMLReader& reader)
@@ -181,10 +201,6 @@ void Constraint::Restore(XMLReader& reader)
181201
Name = reader.getAttribute<const char*>("Name");
182202
Type = reader.getAttribute<ConstraintType>("Type");
183203
Value = reader.getAttribute<double>("Value");
184-
First = reader.getAttribute<long>("First");
185-
FirstPos = reader.getAttribute<PointPos>("FirstPos");
186-
Second = reader.getAttribute<long>("Second");
187-
SecondPos = reader.getAttribute<PointPos>("SecondPos");
188204

189205
if (this->Type == InternalAlignment) {
190206
AlignmentType = reader.getAttribute<InternalAlignmentType>("InternalAlignmentType");
@@ -197,12 +213,6 @@ void Constraint::Restore(XMLReader& reader)
197213
AlignmentType = Undef;
198214
}
199215

200-
// read the third geo group if present
201-
if (reader.hasAttribute("Third")) {
202-
Third = reader.getAttribute<long>("Third");
203-
ThirdPos = reader.getAttribute<PointPos>("ThirdPos");
204-
}
205-
206216
// Read the distance a constraint label has been moved
207217
if (reader.hasAttribute("LabelDistance")) {
208218
LabelDistance = (float)reader.getAttribute<double>("LabelDistance");
@@ -223,38 +233,113 @@ void Constraint::Restore(XMLReader& reader)
223233
if (reader.hasAttribute("IsActive")) {
224234
isActive = reader.getAttribute<bool>("IsActive");
225235
}
236+
237+
if (reader.hasAttribute("ElementIds") && reader.hasAttribute("ElementPositions")) {
238+
auto splitAndClean = [](std::string_view input) {
239+
const char delimiter = ' ';
240+
241+
auto tokens = input | std::views::split(delimiter)
242+
| std::views::transform([](auto&& subrange) {
243+
// workaround due to lack of std::ranges::to in c++20
244+
std::string token;
245+
auto size = std::ranges::distance(subrange);
246+
token.reserve(size);
247+
for (char c : subrange) {
248+
token.push_back(c);
249+
}
250+
return token;
251+
})
252+
| std::views::filter([](const std::string& s) {
253+
return !s.empty();
254+
});
255+
256+
return std::vector<std::string>(tokens.begin(), tokens.end());
257+
};
258+
259+
const std::string elementIds = reader.getAttribute<const char*>("ElementIds");
260+
const std::string elementPositions = reader.getAttribute<const char*>("ElementPositions");
261+
262+
const auto ids = splitAndClean(elementIds);
263+
const auto positions = splitAndClean(elementPositions);
264+
265+
if (ids.size() != positions.size()) {
266+
throw Base::ParserError(fmt::format("ElementIds and ElementPositions do not match in "
267+
"size. Got {} ids and {} positions.",
268+
ids.size(),
269+
positions.size()));
270+
}
271+
272+
elements.clear();
273+
for (size_t i = 0; i < std::min(ids.size(), positions.size()); ++i) {
274+
const int geoId {std::stoi(ids[i])};
275+
const PointPos pos {static_cast<PointPos>(std::stoi(positions[i]))};
276+
addElement(GeoElementId(geoId, pos));
277+
}
278+
}
279+
280+
// Ensure we have at least 3 elements
281+
while (getElementsSize() < 3) {
282+
addElement(GeoElementId(GeoEnum::GeoUndef, PointPos::none));
283+
}
284+
285+
// Load deprecated First, Second, Third elements
286+
// These take precedence over the new elements
287+
// Even though these are deprecated, we still need to read them
288+
// for compatibility with old files.
289+
{
290+
constexpr std::array<const char*, 3> names = {"First", "Second", "Third"};
291+
constexpr std::array<const char*, 3> posNames = {"FirstPos", "SecondPos", "ThirdPos"};
292+
static_assert(names.size() == posNames.size());
293+
294+
for (size_t i = 0; i < names.size(); ++i) {
295+
if (reader.hasAttribute(names[i])) {
296+
const int geoId {reader.getAttribute<int>(names[i])};
297+
const PointPos pos {reader.getAttribute<PointPos>(posNames[i])};
298+
setElement(i, GeoElementId(geoId, pos));
299+
}
300+
}
301+
}
226302
}
227303

228304
void Constraint::substituteIndex(int fromGeoId, int toGeoId)
229305
{
230-
if (this->First == fromGeoId) {
231-
this->First = toGeoId;
232-
}
233-
if (this->Second == fromGeoId) {
234-
this->Second = toGeoId;
306+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
307+
for (size_t i = 0; i < elements.size(); ++i) {
308+
const GeoElementId element = getElement(i);
309+
if (element.GeoId == fromGeoId) {
310+
setElement(i, GeoElementId(toGeoId, element.Pos));
311+
}
235312
}
236-
if (this->Third == fromGeoId) {
237-
this->Third = toGeoId;
313+
#else
314+
for (auto& element : elements) {
315+
if (element.GeoId == fromGeoId) {
316+
element = GeoElementId(toGeoId, element.Pos);
317+
}
238318
}
319+
#endif
239320
}
240321

241322
void Constraint::substituteIndexAndPos(int fromGeoId,
242323
PointPos fromPosId,
243324
int toGeoId,
244325
PointPos toPosId)
245326
{
246-
if (this->First == fromGeoId && this->FirstPos == fromPosId) {
247-
this->First = toGeoId;
248-
this->FirstPos = toPosId;
249-
}
250-
if (this->Second == fromGeoId && this->SecondPos == fromPosId) {
251-
this->Second = toGeoId;
252-
this->SecondPos = toPosId;
327+
const GeoElementId from {fromGeoId, fromPosId};
328+
329+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
330+
for (size_t i = 0; i < elements.size(); ++i) {
331+
const GeoElementId element = getElement(i);
332+
if (element == from) {
333+
setElement(i, GeoElementId(toGeoId, toPosId));
334+
}
253335
}
254-
if (this->Third == fromGeoId && this->ThirdPos == fromPosId) {
255-
this->Third = toGeoId;
256-
this->ThirdPos = toPosId;
336+
#else
337+
for (auto& element : elements) {
338+
if (element == from) {
339+
element = GeoElementId(toGeoId, toPosId);
340+
}
257341
}
342+
#endif
258343
}
259344

260345
std::string Constraint::typeToString(ConstraintType type)
@@ -266,3 +351,91 @@ std::string Constraint::internalAlignmentTypeToString(InternalAlignmentType alig
266351
{
267352
return internalAlignmentType2str[alignment];
268353
}
354+
355+
bool Constraint::involvesGeoId(int geoId) const
356+
{
357+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
358+
auto elements =
359+
std::views::iota(size_t {0}, this->elements.size()) | std::views::transform([&](size_t i) {
360+
return getElement(i);
361+
});
362+
#endif
363+
return std::ranges::any_of(elements, [geoId](const auto& element) {
364+
return element.GeoId == geoId;
365+
});
366+
}
367+
/// utility function to check if (`geoId`, `posId`) is one of the points/curves
368+
bool Constraint::involvesGeoIdAndPosId(int geoId, PointPos posId) const
369+
{
370+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
371+
auto elements =
372+
std::views::iota(size_t {0}, this->elements.size()) | std::views::transform([&](size_t i) {
373+
return getElement(i);
374+
});
375+
#endif
376+
return std::ranges::find(elements, GeoElementId(geoId, posId)) != elements.end();
377+
}
378+
379+
GeoElementId Constraint::getElement(size_t index) const
380+
{
381+
if (index >= elements.size()) {
382+
throw Base::IndexError("Constraint::getElement index out of range");
383+
}
384+
385+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
386+
if (index < 3) {
387+
switch (index) {
388+
case 0:
389+
return GeoElementId(First, FirstPos);
390+
case 1:
391+
return GeoElementId(Second, SecondPos);
392+
case 2:
393+
return GeoElementId(Third, ThirdPos);
394+
}
395+
}
396+
#endif
397+
return elements[index];
398+
}
399+
void Constraint::setElement(size_t index, GeoElementId element)
400+
{
401+
if (index >= elements.size()) {
402+
throw Base::IndexError("Constraint::getElement index out of range");
403+
}
404+
405+
elements[index] = element;
406+
407+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
408+
if (index < 3) {
409+
switch (index) {
410+
case 0:
411+
First = element.GeoId;
412+
FirstPos = element.Pos;
413+
break;
414+
case 1:
415+
Second = element.GeoId;
416+
SecondPos = element.Pos;
417+
break;
418+
case 2:
419+
Third = element.GeoId;
420+
ThirdPos = element.Pos;
421+
break;
422+
}
423+
}
424+
#endif
425+
}
426+
427+
size_t Constraint::getElementsSize() const
428+
{
429+
return elements.size();
430+
}
431+
432+
void Constraint::addElement(GeoElementId element)
433+
{
434+
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
435+
int i = elements.size();
436+
elements.resize(i + 1);
437+
setElement(i, element);
438+
#else
439+
elements.push_back(element);
440+
#endif
441+
}

0 commit comments

Comments
 (0)