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;
4451TYPESYSTEM_SOURCE (Sketcher::Constraint, Base::Persistence)
4552
4653Constraint::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
178198void 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
228304void 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
241322void 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
260345std::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