@@ -14,6 +14,27 @@ namespace {
1414 }
1515}
1616
17+ namespace {
18+ template <typename T, typename Cmp = std::less<T>>
19+ bool has_intersection (const std::set<T, Cmp>& A,
20+ const std::set<T, Cmp>& B) {
21+ auto itA = A.begin ();
22+ auto itB = B.begin ();
23+
24+ while (itA != A.end () && itB != B.end ()) {
25+ if (Cmp ()(*itA, *itB)) {
26+ ++itA;
27+ } else if (Cmp ()(*itB, *itA)) {
28+ ++itB;
29+ } else {
30+ return true ;
31+ }
32+ }
33+ return false ;
34+ }
35+
36+ }
37+
1738taxonomy::loft::ptr ifcopenshell::geometry::make_loft (const Settings& settings_, const IfcUtil::IfcBaseClass* inst, const taxonomy::function_item::ptr& fn, std::vector<cross_section>& cross_sections)
1839{
1940 std::sort (cross_sections.begin (), cross_sections.end ());
@@ -25,7 +46,7 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
2546 // @todo currently only the case is handled where directrix returns a function_item
2647 // @todo this "if" statement is not really required because the function returns at the start if the Directrix is not a function_item function
2748 if (fn) {
28- function_item_evaluator evaluator (settings_,fn);
49+ function_item_evaluator evaluator (settings_, fn);
2950 double start = std::max (0 ., cross_sections.front ().dist_along );
3051 double end = std::min (fn->length (), cross_sections.back ().dist_along );
3152
@@ -45,21 +66,24 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
4566 // parameter is minimum number of steps
4667 num_steps = (size_t )std::ceil (param);
4768 }
69+ auto delta_step = curve_length / num_steps;
4870 std::vector<double > longitudes;
4971 for (auto & x : cross_sections) {
5072 longitudes.push_back (x.dist_along );
5173 }
5274 longitudes.push_back (std::numeric_limits<double >::infinity ());
5375 auto profile_index = longitudes.begin ();
5476 for (size_t i = 0 ; i <= num_steps; ++i) {
55- auto dist_along = start + curve_length / num_steps * i;
77+ auto dist_along = start + delta_step * i;
5678 while (dist_along > *(profile_index + 1 )) {
5779 profile_index++;
5880 if (profile_index == longitudes.end ()) {
5981 // @todo handle this?
6082 }
6183 }
6284
85+ const bool is_last_placement_of_this_profile = profile_index + 1 >= longitudes.end () ? false : ((start + delta_step * (i+1 )) > *(profile_index + 1 ));
86+
6387 auto relative_dist_along = (dist_along - *profile_index) / (*(profile_index + 1 ) - *profile_index);
6488 const auto & profile_a = cross_sections[std::distance (longitudes.begin (), profile_index)].section_geometry ;
6589 const auto & offset_a = cross_sections[std::distance (longitudes.begin (), profile_index)].offset ;
@@ -143,28 +167,203 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
143167 } else if (rotation_a != rotation_b) {
144168 Logger::Error (" Direction vectors on cross section placements only supported when used consistently" );
145169 }
170+
146171 taxonomy::loop::ptr w1, w2;
147172 taxonomy::edge::ptr e1 , e2 ;
173+ taxonomy::point3::ptr p1, p2;
174+
148175 for (auto tmp_ : boost::combine (loops_a, loops_b)) {
149176 boost::tie (w1, w2) = tmp_;
150- if (w1->children .size () != w2->children .size ()) {
151- Logger::Warning (" Mismatching number of edges: " +
152- std::to_string (w1->children .size ()) + " vs " +
153- std::to_string (w2->children .size ()),
154- inst
155- );
177+
178+ if (w1->closed != w2->closed ) {
179+ Logger::Warning (" Mismatching closed property on loops" , inst);
180+ return nullptr ;
181+ }
182+
183+ if (w1->tags .is_initialized () != w2->tags .is_initialized ()) {
184+ Logger::Warning (" Mismatching availability tags on loops" , inst);
156185 return nullptr ;
186+ }
187+
188+ if (w1->tags ) {
189+ // check uniqueness
190+ std::set<std::string> tags_seen;
191+ for (const auto & t : *w1->tags ) {
192+ if (tags_seen.find (t) != tags_seen.end ()) {
193+ Logger::Warning (" Duplicate tag '" + t + " ' on loft profile" , inst);
194+ return nullptr ;
195+ }
196+ tags_seen.insert (t);
197+ }
157198 }
158- std::vector<taxonomy::point3::ptr> points;
159- for (auto tmp__ : boost::combine (w1->children , w2->children )) {
160- boost::tie (e1 , e2 ) = tmp__;
161- auto & p1 = boost::get<taxonomy::point3::ptr>(e1 ->start );
162- auto & p2 = boost::get<taxonomy::point3::ptr>(e2 ->start );
163-
164- auto p3 = (lerp (p1->ccomponents (), p2->ccomponents (), relative_dist_along) + interpolated_offset).eval ();
165- // auto p4 = (interpolated_rotation * p3).eval();
166- points.push_back (taxonomy::make<taxonomy::point3>(p3));
199+
200+ if (w2->tags ) {
201+ // check uniqueness
202+ std::set<std::string> tags_seen;
203+ for (const auto & t : *w2->tags ) {
204+ if (tags_seen.find (t) != tags_seen.end ()) {
205+ Logger::Warning (" Duplicate tag '" + t + " ' on loft profile" , inst);
206+ return nullptr ;
207+ }
208+ tags_seen.insert (t);
209+ }
210+ }
211+
212+ std::map<std::string, taxonomy::point3::ptr> tag_to_point_on_w1, tag_to_point_on_w2;
213+
214+ auto loop_to_points = [](const taxonomy::loop::ptr& loop, const boost::optional<std::vector<std::string>>& input_tags) -> std::pair<std::vector<taxonomy::point3::ptr>, std::vector<std::set<std::string>>> {
215+ std::vector<taxonomy::point3::ptr> points;
216+ std::vector<std::set<std::string>> tags;
217+ std::vector<std::string>::const_iterator tag_it;
218+
219+ if (!loop->closed .get_value_or (false )) {
220+ points = {boost::get<taxonomy::point3::ptr>(loop->children [0 ]->start )};
221+ if (input_tags) {
222+ tags = {{input_tags->front ()}};
223+ tag_it = ++input_tags->begin ();
224+ }
225+ }
226+ for (auto & e : loop->children ) {
227+ const auto & p1 = boost::get<taxonomy::point3::ptr>(e->start );
228+ const auto & p2 = boost::get<taxonomy::point3::ptr>(e->end );
229+ if (input_tags && p1->ccomponents () == p2->ccomponents ()) {
230+ tags.back ().insert (*tag_it);
231+ ++tag_it;
232+ } else {
233+ points.push_back (p2);
234+ if (input_tags) {
235+ tags.emplace_back ();
236+ tags.back ().insert (*tag_it);
237+ ++tag_it;
238+ }
239+ }
240+ }
241+ if (!input_tags) {
242+ if (loop->closed .get_value_or (false )) {
243+ // close polygon by referencing first point
244+ points.push_back (points.front ());
245+ }
246+ }
247+ return {points, tags};
248+ };
249+
250+ auto combine_tags = [](const std::vector<std::set<std::string>>& tag_sets) -> std::set<std::string> {
251+ return std::accumulate (
252+ tag_sets.begin (), tag_sets.end (), std::set<std::string>{},
253+ [](std::set<std::string> acc,
254+ const std::set<std::string>& m) {
255+ acc.insert (m.begin (), m.end ());
256+ return acc;
257+ });
258+ };
259+
260+ auto join_tags = [](const std::set<std::string>& tag_set) -> std::string {
261+ std::string result;
262+ for (auto it = tag_set.begin (); it != tag_set.end (); ++it) {
263+ if (it != tag_set.begin ()) {
264+ result += " , " ;
265+ }
266+ result += *it;
267+ }
268+ return result;
269+ };
270+
271+ auto [w1_points, w1_tags] = loop_to_points (w1, w1->tags );
272+ auto [w2_points, w2_tags] = loop_to_points (w2, w2->tags );
273+
274+ if (w1->tags && w2->tags ) {
275+ {
276+ auto it = w1_points.begin ();
277+ auto jt = w1_tags.begin ();
278+ while (it != w1_points.end () && jt != w1_tags.end ()) {
279+ for (auto & t : *jt) {
280+ tag_to_point_on_w1[t] = *it;
281+ }
282+ ++it;
283+ ++jt;
284+ }
285+ }
286+
287+ {
288+ auto it = w2_points.begin ();
289+ auto jt = w2_tags.begin ();
290+ while (it != w2_points.end () && jt != w2_tags.end ()) {
291+ for (auto & t : *jt) {
292+ tag_to_point_on_w2[t] = *it;
293+ }
294+ ++it;
295+ ++jt;
296+ }
297+ }
298+
299+ auto w1_tags_combined = combine_tags (w1_tags);
300+ auto w2_tags_combined = combine_tags (w2_tags);
301+
302+ // For every point (which can have multiple tags in case of 0-width edges) there needs to be a corresponding point on the other profile
303+
304+ for (auto & p1_tags : w1_tags) {
305+ if (!has_intersection (p1_tags, w2_tags_combined)) {
306+ Logger::Warning (" No matching tags found on loft profiles: " + join_tags (p1_tags) + " not in " + join_tags (w2_tags_combined), inst);
307+ return nullptr ;
308+ }
309+ }
310+
311+ for (auto & p2_tags : w2_tags) {
312+ if (!has_intersection (p2_tags, w1_tags_combined)) {
313+ Logger::Warning (" No matching tags found on loft profiles: " + join_tags (p2_tags) + " not in " + join_tags (w1_tags_combined), inst);
314+ return nullptr ;
315+ }
316+ }
317+ } else {
318+ if (w1->children .size () != w2->children .size ()) {
319+ Logger::Warning (" Mismatching number of edges: " +
320+ std::to_string (w1->children .size ()) + " vs " +
321+ std::to_string (w2->children .size ()),
322+ inst);
323+ return nullptr ;
324+ }
167325 }
326+
327+ std::vector<taxonomy::point3::ptr> points;
328+
329+ std::vector<std::string> common_tags_vec;
330+ if (w1->tags ) {
331+ std::set<std::string> common_tags;
332+ for (const auto & t : *w1->tags ) {
333+ if (tag_to_point_on_w2.find (t) == tag_to_point_on_w2.end ()) {
334+ continue ;
335+ }
336+
337+ const auto & p1 = tag_to_point_on_w1[t];
338+ const auto & p2 = tag_to_point_on_w2[t];
339+
340+ auto p3 = (lerp (p1->ccomponents (), p2->ccomponents (), relative_dist_along) + interpolated_offset).eval ();
341+
342+ std::set<std::string> tags_for_this_point_on_subsequent_profile = {t};
343+
344+ if (is_last_placement_of_this_profile) {
345+ for (auto & ts : w2_tags) {
346+ if (ts.find (t) != ts.end ()) {
347+ tags_for_this_point_on_subsequent_profile = ts;
348+ }
349+ }
350+ }
351+
352+ for (auto & x : tags_for_this_point_on_subsequent_profile) {
353+ points.push_back (taxonomy::make<taxonomy::point3>(p3));
354+ common_tags_vec.push_back (x);
355+ }
356+ }
357+ } else {
358+ for (auto tmp__ : boost::combine (w1_points, w2_points)) {
359+ boost::tie (p1, p2) = tmp__;
360+ auto p3 = (lerp (p1->ccomponents (), p2->ccomponents (), relative_dist_along) + interpolated_offset).eval ();
361+ points.push_back (taxonomy::make<taxonomy::point3>(p3));
362+ }
363+ }
364+
365+ /*
366+ // This is handled in the loop_to_points() function above
168367 if (!points.empty()) {
169368 if (!w1->closed.get_value_or(true) && !w2->closed.get_value_or(true)) {
170369 // open polygon, add last point
@@ -178,12 +377,17 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
178377 points.push_back(points.front());
179378 }
180379 }
380+ */
181381
182382 auto interpolated_loop = polygon_from_points (points);
183- interpolated_loop->external = w1->external ;
184383 if (interpolated->kind () == taxonomy::FACE) {
185- std::static_pointer_cast<taxonomy::face>(interpolated)->children .push_back (interpolated_loop);
384+ interpolated_loop->external = w1->external ;
385+ std::static_pointer_cast<taxonomy::face>(interpolated)->children .push_back (interpolated_loop);
186386 } else {
387+ if (w1->tags ) {
388+ std::static_pointer_cast<taxonomy::loop>(interpolated)->tags = common_tags_vec;
389+ }
390+ std::static_pointer_cast<taxonomy::loop>(interpolated)->closed = w1->closed ;
187391 std::static_pointer_cast<taxonomy::loop>(interpolated)->children = interpolated_loop->children ;
188392 }
189393 }
@@ -227,6 +431,19 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
227431 auto m = (m4b * loft->children .back ()->matrix ->ccomponents ()).eval ();
228432 loft->children .back ()->matrix ->components () = m;
229433 }
434+
435+ auto find_closest = [](const std::vector<double >& v, double target) -> std::vector<double >::const_iterator {
436+ auto it = std::lower_bound (v.begin (), v.end (), target);
437+
438+ if (it == v.begin ()) {
439+ return it;
440+ }
441+
442+ double after = *it;
443+ double before = *(it - 1 );
444+
445+ return (std::abs (after - target) < std::abs (target - before)) ? it : (it - 1 );
446+ };
230447 }
231448
232449 return loft;
0 commit comments