diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 7a1f6c717e..7abc1d42ea 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -33,3 +33,6 @@ set_target_properties(IfcAdvancedHouse PROPERTIES FOLDER Examples) endif() +ADD_EXECUTABLE(IfcAlignment IfcAlignment.cpp) +TARGET_LINK_LIBRARIES(IfcAlignment ${IFCOPENSHELL_LIBRARIES}) +set_target_properties(IfcAlignment PROPERTIES FOLDER Examples) diff --git a/src/examples/IfcAlignment.cpp b/src/examples/IfcAlignment.cpp new file mode 100644 index 0000000000..4c4d77c26a --- /dev/null +++ b/src/examples/IfcAlignment.cpp @@ -0,0 +1,398 @@ +// This example illustrates the basic of building an alignment model. +// The alignment is based on "Bridge Geometry Manual", April 2022 +// US Department of Transportation, Federal Highway Administration (FHWA) +// https://www.fhwa.dot.gov/bridge/pubs/hif22034.pdf +// +// Sections and page number for this document are cited in the code comments. + +// Disable warnings coming from IfcOpenShell +#pragma warning(disable:4018 4267 4250 4984 4985) + +#include "../ifcparse/IfcHierarchyHelper.h" +#include "../ifcparse/Ifc4x3_add2.h" + +#include + +const double PI = boost::math::constants::pi(); +double ToRadian(double deg) { return PI * deg / 180; } + +#define Schema Ifc4x3_add2 + +// creates geometry and business logic segments for horizontal alignment tangent runs +std::pair create_tangent(typename Schema::IfcCartesianPoint* p,double dir,double length) +{ + // geometry + auto parent_curve = new Schema::IfcLine( + new Schema::IfcCartesianPoint(std::vector({0,0})), + new Schema::IfcVector(new Schema::IfcDirection(std::vector{1.0, 0.0}), 1.0)); + + auto curve_segment = new Schema::IfcCurveSegment( + Schema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, + new Schema::IfcAxis2Placement2D(p, new Schema::IfcDirection(std::vector{cos(dir), sin(dir)})), + new Schema::IfcLengthMeasure(0.0), // start + new Schema::IfcLengthMeasure(length), + parent_curve); + + // business logic + auto design_parameters = new Schema::IfcAlignmentHorizontalSegment( + boost::none, boost::none, p, dir, 0.0, 0.0, length, boost::none, + Schema::IfcAlignmentHorizontalSegmentTypeEnum::IfcAlignmentHorizontalSegmentType_LINE); + + auto alignment_segment = new Schema::IfcAlignmentSegment( + IfcParse::IfcGlobalId(), nullptr, boost::none, boost::none, boost::none, nullptr, nullptr, design_parameters); + + return { curve_segment,alignment_segment }; +} + +// creates geometry and business logic segments for horizontal alignment horizonal curves +std::pair create_hcurve(typename Schema::IfcCartesianPoint* pc, typename Schema::IfcCartesianPoint* cc, double dir, double radius,double lc) +{ + // geometry + double sign = radius / fabs(radius); + auto parent_curve = new Schema::IfcCircle( + new Schema::IfcAxis2Placement2D(cc, new Schema::IfcDirection(std::vector{cos(dir - sign*PI / 2), sin(dir - sign*PI / 2)})), + fabs(radius)); + + auto curve_segment = new Schema::IfcCurveSegment( + Schema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, + new Schema::IfcAxis2Placement2D(new Schema::IfcCartesianPoint(std::vector({ 0,0 })), new Schema::IfcDirection(std::vector{1, 0})), + new Schema::IfcLengthMeasure(0.0), + new Schema::IfcLengthMeasure(sign*lc), + parent_curve); + + // business logic + auto design_parameters = new Schema::IfcAlignmentHorizontalSegment(boost::none, boost::none, pc, dir, radius, radius, lc, boost::none, Schema::IfcAlignmentHorizontalSegmentTypeEnum::IfcAlignmentHorizontalSegmentType_CIRCULARARC); + auto alignment_segment = new Schema::IfcAlignmentSegment(IfcParse::IfcGlobalId(), nullptr,boost::none, boost::none, boost::none, nullptr, nullptr, design_parameters); + + return { curve_segment,alignment_segment }; +} + +// creates geometry and business logic segments for vertical profile gradient runs +std::pair create_gradient(typename Schema::IfcCartesianPoint* p,double slope,double length) +{ + // geometry + auto l = sqrt(1.0 + slope * slope); + auto dx = 1.0 / l; + auto dy = slope / l; + + auto parent_curve = new Schema::IfcLine( + new Schema::IfcCartesianPoint(std::vector({ 0,p->Coordinates()[1]})), + new Schema::IfcVector(new Schema::IfcDirection(std::vector{dx,dy}), 1.0)); + + auto curve_segment = new Schema::IfcCurveSegment( + Schema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, + new Schema::IfcAxis2Placement2D(new Schema::IfcCartesianPoint(std::vector({ 0, 0 })), new Schema::IfcDirection(std::vector{1,0})), + new Schema::IfcLengthMeasure(0.0), // start + new Schema::IfcLengthMeasure(length), + parent_curve); + + // business logic + auto design_parameters = new Schema::IfcAlignmentVerticalSegment(boost::none, boost::none, p->Coordinates()[0], length, p->Coordinates()[1], slope, slope, boost::none, Schema::IfcAlignmentVerticalSegmentTypeEnum::IfcAlignmentVerticalSegmentType_CONSTANTGRADIENT); + auto alignment_segment = new Schema::IfcAlignmentSegment(IfcParse::IfcGlobalId(), nullptr, boost::none, boost::none, boost::none, nullptr, nullptr, design_parameters); + + return { curve_segment,alignment_segment }; +} + +// creates geometry and business logic segments for vertical profile parabolic vertical curves +std::pair create_vcurve(typename Schema::IfcCartesianPoint* p, double start_slope,double end_slope, double length) +{ + // geometry + double A = p->Coordinates()[1]; + double B = start_slope; + double C = (end_slope - start_slope) / (2 * length); + auto parent_curve_placement = new Schema::IfcAxis2Placement2D(new Schema::IfcCartesianPoint(std::vector{0.0, 0.0}), new Schema::IfcDirection(std::vector{1.0, 0.0})); + auto parent_curve = new Schema::IfcPolynomialCurve(parent_curve_placement, std::vector{0.0, 1.0}, std::vector{A,B,C}, boost::none); + auto segment_curve_placement = new Schema::IfcAxis2Placement2D(new Schema::IfcCartesianPoint(std::vector{0.0, 0.0}), new Schema::IfcDirection(std::vector{1.0, 0.0})); + auto curve_segment = new Schema::IfcCurveSegment(Schema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, segment_curve_placement, + new Schema::IfcLengthMeasure(p->Coordinates()[0]), + new Schema::IfcLengthMeasure(length), + parent_curve); + + // business logic + double k = (end_slope - start_slope) / length; + auto design_patameters = new Schema::IfcAlignmentVerticalSegment(boost::none, boost::none, p->Coordinates()[0], length, p->Coordinates()[1], start_slope, end_slope, 1 / k, Schema::IfcAlignmentVerticalSegmentTypeEnum::IfcAlignmentVerticalSegmentType_PARABOLICARC); + auto alignment_segment = new Schema::IfcAlignmentSegment(IfcParse::IfcGlobalId(), nullptr, boost::none, boost::none, boost::none, nullptr, nullptr, design_patameters); + + return { curve_segment,alignment_segment }; +} + +int main() +{ + IfcHierarchyHelper file; + + std::vector file_description; + std::ostringstream os; + os << "ViewDefinition[Alignment-basedReferenceView]" << std::ends; + file_description.push_back(os.str().c_str()); + file.header().file_description().description(file_description); + + auto project = file.addProject(); + project->setName(std::string("FHWA Bridge Geometry Manual Example Alignment")); + + // set up project units for feet + // the call to file.addProject() sets up length units as millimeter. + auto units_in_context = project->UnitsInContext(); + auto units = units_in_context->Units(); + auto begin = units->begin(); + auto iter = begin; + auto end = units->end(); + for (; iter != end; iter++) + { + auto unit = *iter; + if (unit->as() && unit->as()->UnitType() == Schema::IfcUnitEnum::IfcUnit_LENGTHUNIT) + { + auto dimensions = new Schema::IfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0); + file.addEntity(dimensions); + + auto conversion_factor = new Schema::IfcMeasureWithUnit(new Schema::IfcLengthMeasure(304.80), unit->as()); + file.addEntity(conversion_factor); + + auto conversion_based_unit = new Schema::IfcConversionBasedUnit(dimensions, Schema::IfcUnitEnum::IfcUnit_LENGTHUNIT, "FEET", conversion_factor); + file.addEntity(conversion_based_unit); + + units->remove(unit); // remove the millimeter unit + units->push(conversion_based_unit); // add the feet unit + units_in_context->setUnits(units); // update the UnitsInContext + + break; // Done!, the length unit was found, so break out of the loop + } + } + + auto site = file.addSite(project, nullptr); + + + // + // Define horizontal alignment + // + + // define key points + // B.1.4 pg 212 + auto pob = file.addDoublet(500, 2500); // beginning + auto pc1 = file.addDoublet(2142.237995, 1436.014820); // Point of curve (PC), Curve #1 + auto cc1 = file.addDoublet(2685.979298, 2275.267700); // Center of circle (CC), Curve #1 + auto pt1 = file.addDoublet(3660.446123, 2050.736173); // Point of tangent (PT), Curve #1 + auto pc2 = file.addDoublet(4084.115884, 3889.462938); + auto cc2 = file.addDoublet(5302.199416, 3608.798529); + auto pt2 = file.addDoublet(5469.395067, 4847.566310); + auto pc3 = file.addDoublet(7019.971367, 4638.286073); + auto cc3 = file.addDoublet(6892.902672, 3696.822560); + auto pt3 = file.addDoublet(7790.932128, 4006.730765); + auto poe = file.addDoublet(8480, 2010); // ending + + // define tangent runs and curve lengths + double run_1 = 1956.785654; + double lc_1 = 1919.222667; + double run_2 = 1886.905454; + double lc_2 = 1848.115835; + double run_3 = 1564.635765; + double lc_3 = 1049.119737; + double run_4 = 2112.285084; + + // define curve radii + double rc_1 = 1000; + double rc_2 = -1250; // negative radius for curves to the right + double rc_3 = -950; + + // curve delta angles + // pg 17, Eq 2.16 - 2.19 + double angle_1 = ToRadian(327.0613); + double angle_2 = ToRadian(77.0247); + double angle_3 = ToRadian(352.3133); + double angle_4 = ToRadian(289.0395); + + // geometric representations + typename aggregate_of::ptr horizontal_curve_segments(new aggregate_of()); + typename aggregate_of::ptr horizontal_segments(new aggregate_of()); + + // POB to PC1 + auto curve_segment_1 = create_tangent(pob, angle_1, run_1); + horizontal_curve_segments->push(curve_segment_1.first); + horizontal_segments->push(curve_segment_1.second); + + // Curve 1 + auto curve_segment_2 = create_hcurve(pc1, cc1,angle_1,rc_1,lc_1); + horizontal_curve_segments->push(curve_segment_2.first); + horizontal_segments->push(curve_segment_2.second); + + // PT1 to PC2 + auto curve_segment_3 = create_tangent(pt1, angle_2, run_2); + horizontal_curve_segments->push(curve_segment_3.first); + horizontal_segments->push(curve_segment_3.second); + + // Curve 2 + auto curve_segment_4 = create_hcurve(pc2, cc2, angle_2, rc_2, lc_2); + horizontal_curve_segments->push(curve_segment_4.first); + horizontal_segments->push(curve_segment_4.second); + + // PT2 to PC3 + auto curve_segment_5 = create_tangent(pt2, angle_3, run_3); + horizontal_curve_segments->push(curve_segment_5.first); + horizontal_segments->push(curve_segment_5.second); + + // Curve 3 + auto curve_segment_6 = create_hcurve(pc3, cc3, angle_3, rc_3, lc_3); + horizontal_curve_segments->push(curve_segment_6.first); + horizontal_segments->push(curve_segment_6.second); + + // PT3 to POE + auto curve_segment_7 = create_tangent(pt3, angle_4, run_4); + horizontal_curve_segments->push(curve_segment_7.first); + horizontal_segments->push(curve_segment_7.second); + + // Zero-length terminator segment + auto terminator_segment = create_tangent(poe, angle_4, 0.0); + terminator_segment.first->setTransition(Schema::IfcTransitionCode::IfcTransitionCode_DISCONTINUOUS); + horizontal_curve_segments->push(terminator_segment.first); + horizontal_segments->push(terminator_segment.second); + + auto composite_curve = new Schema::IfcCompositeCurve(horizontal_curve_segments, false/*not self-intersecting*/); + file.addEntity(composite_curve); + + typename aggregate_of::ptr representation_items(new aggregate_of()); + representation_items->push(composite_curve); + + auto geometric_representation_context = file.getRepresentationContext(std::string("3D")); // creates the representation context if it doesn't already exist + auto representation_subcontext = new Schema::IfcGeometricRepresentationSubContext(std::string("Axis"), std::string("Model"), geometric_representation_context, boost::none, Schema::IfcGeometricProjectionEnum::IfcGeometricProjection_GRAPH_VIEW, boost::none); + file.addEntity(representation_subcontext); + auto shape_representation_2D = new Schema::IfcShapeRepresentation(representation_subcontext, std::string("FootPrint"), std::string("Curve2D"), representation_items); + file.addEntity(shape_representation_2D); + + auto horizontal_alignment = new Schema::IfcAlignmentHorizontal(IfcParse::IfcGlobalId(), nullptr, std::string("Horizontal Alignment"), boost::none, boost::none, nullptr, nullptr/*representation*/); + file.addEntity(horizontal_alignment); + + auto nests_horizontal_segments = new Schema::IfcRelNests(IfcParse::IfcGlobalId(), nullptr, boost::none, std::string("Nests horizontal alignment segments with horizontal alignment"), horizontal_alignment, horizontal_segments); + file.addEntity(nests_horizontal_segments); + + // + // Define vertical profile segments + // + + typename aggregate_of::ptr vertical_curve_segments(new aggregate_of()); + typename aggregate_of::ptr vertical_segments(new aggregate_of()); + + // define key profile points + auto vpob = file.addDoublet(0.0, 100.0); // beginning + auto vpc1 = file.addDoublet(1200.0, 121.0); // Vertical Curve Point (VPC), Vertical Curve #1 + auto vpt1 = file.addDoublet(2800.0, 127.0); // Vertical Curve Tangent (VPT), Vertical Curve #1 + auto vpc2 = file.addDoublet(4400.0, 111.0); + auto vpt2 = file.addDoublet(5600.0, 117.0); + auto vpc3 = file.addDoublet(6400.0, 133.0); + auto vpt3 = file.addDoublet(8400.0, 133.0); + auto vpc4 = file.addDoublet(9400.0, 113.0); + auto vpt4 = file.addDoublet(10200.0, 103.0); + auto vpoe = file.addDoublet(12800.0, 90.0); // ending + + // Grade start to VPC1 + auto vertical_profile_segment_1 = create_gradient(vpob, 1.75 / 100, 1200); + vertical_curve_segments->push(vertical_profile_segment_1.first); + vertical_segments->push(vertical_profile_segment_1.second); + + // Vertical Curve 1 + auto vertical_profile_segment_2 = create_vcurve(vpc1, 1.75 / 100, -1.0 / 100, 1600); + vertical_curve_segments->push(vertical_profile_segment_2.first); + vertical_segments->push(vertical_profile_segment_2.second); + + // Grade VPT1 to VPC2 + auto vertical_profile_segment_3 = create_gradient(vpt1, -1.0 / 100, 1600); + vertical_curve_segments->push(vertical_profile_segment_3.first); + vertical_segments->push(vertical_profile_segment_3.second); + + // Vertical Curve 2 + auto vertical_profile_segment_4 = create_vcurve(vpc2, -1.0 / 100, 2.0 / 100, 1200); + vertical_curve_segments->push(vertical_profile_segment_4.first); + vertical_segments->push(vertical_profile_segment_4.second); + + // Grade PVT2 to VPC3 + auto vertical_profile_segment_5 = create_gradient(vpt2, 2.0 / 100, 800); + vertical_curve_segments->push(vertical_profile_segment_5.first); + vertical_segments->push(vertical_profile_segment_5.second); + + // Vertical Curve 3 + auto vertical_profile_segment_6 = create_vcurve(vpc3, 2.0 / 100, -2.0 / 100, 2000); + vertical_curve_segments->push(vertical_profile_segment_6.first); + vertical_segments->push(vertical_profile_segment_6.second); + + // Grade PVT3 to VPC4 + auto vertical_profile_segment_7 = create_gradient(vpt3, -2.0 / 100, 1000); + vertical_curve_segments->push(vertical_profile_segment_7.first); + vertical_segments->push(vertical_profile_segment_7.second); + + // Vertical Curve 4 + auto vertical_profile_segment_8 = create_vcurve(vpc4, -2.0 / 100, -0.5 / 100, 800); + vertical_curve_segments->push(vertical_profile_segment_8.first); + vertical_segments->push(vertical_profile_segment_8.second); + + // Grade VPT4 to End + auto vertical_profile_segment_9 = create_gradient(vpt4, -0.5 / 100, 2600); + vertical_curve_segments->push(vertical_profile_segment_9.first); + vertical_segments->push(vertical_profile_segment_9.second); + + // Zero-length terminator + auto vertical_terminator_segment = create_gradient(vpoe, -0.5 / 100, 0.0); + vertical_curve_segments->push(vertical_terminator_segment.first); + vertical_segments->push(vertical_terminator_segment.second); + + auto vertical_profile = new Schema::IfcAlignmentVertical(IfcParse::IfcGlobalId(), nullptr, std::string("Vertical Alignment"), boost::none, boost::none, file.getSingle(), nullptr); + file.addEntity(vertical_profile); + + auto nests_vertical_segments = new Schema::IfcRelNests(IfcParse::IfcGlobalId(), nullptr, boost::none, std::string("Nests vertical alignment segments with vertical alignment"), vertical_profile, vertical_segments); + file.addEntity(nests_vertical_segments); + + + typename aggregate_of::ptr representation_items2(new aggregate_of()); + + auto gradient_curve = new Schema::IfcGradientCurve(vertical_curve_segments, false, composite_curve, nullptr); + representation_items2->push(gradient_curve); + + + auto shape_representation_3D = new Schema::IfcShapeRepresentation(representation_subcontext, std::string("Axis"), std::string("Curve3D"), representation_items2); + file.addEntity(shape_representation_3D); + + typename aggregate_of::ptr representations(new aggregate_of()); + representations->push(shape_representation_2D); // 2D alignment geometry + representations->push(shape_representation_3D); // 3D alignment geometry + auto alignment_product = new Schema::IfcProductDefinitionShape(std::string("Alignment Product Definition Shape"), boost::none, representations); + + auto local_placement = site->ObjectPlacement(); + if (!local_placement) + { + local_placement = file.addLocalPlacement(); + } + + auto alignment = new Schema::IfcAlignment(IfcParse::IfcGlobalId(), nullptr, std::string("Example Alignment"), boost::none, boost::none, local_placement, alignment_product, boost::none); + file.addEntity(alignment); + + file.relatePlacements(site, horizontal_alignment); + file.relatePlacements(site, vertical_profile); + file.relatePlacements(site, alignment); + + // 4.1.4.4.1 Alignments nest horizontal and vertical layouts + // https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Object_Composition/Nesting/Alignment_Layouts/content.html + typename aggregate_of::ptr alignment_layout_list(new aggregate_of()); + alignment_layout_list->push(horizontal_alignment); + alignment_layout_list->push(vertical_profile); + + auto nests_alignment_layouts = new Schema::IfcRelNests(IfcParse::IfcGlobalId(), nullptr, std::string("Nest horizontal and vertical alignment layouts with the alignment"), boost::none, alignment, alignment_layout_list); + file.addEntity(nests_alignment_layouts); + + // IFC 4.1.4.1.1 "Every IfcAlignment must be related to IfcProject using the IfcRelAggregates relationship" + // https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Object_Composition/Aggregation/Alignment_Aggregation_To_Project/content.html + // IfcProject <-> IfcRelAggregates <-> IfcAlignment + typename aggregate_of::ptr list_of_alignments_in_project(new aggregate_of()); + list_of_alignments_in_project->push(alignment); + auto aggregate_alignments_with_project = new Schema::IfcRelAggregates(IfcParse::IfcGlobalId(), nullptr, std::string("Alignments in project"), boost::none, project, list_of_alignments_in_project); + file.addEntity(aggregate_alignments_with_project); + + // IFC 4.1.5.1 alignment is referenced in spatial structure of an IfcSpatialElement. In this case IfcSite is the highest level IfcSpatialElement + // https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Object_Connectivity/Alignment_Spatial_Reference/content.html + // IfcSite <-> IfcRelReferencedInSpatialStructure <-> IfcAlignment + // This means IfcAlignment is not part of the IfcSite (it is not an aggregate component) but instead IfcAlignment is used within + // the IfcSite by reference. This implies an IfcAlignment can traverse many IfcSite instances within an IfcProject + typename Schema::IfcSpatialReferenceSelect::list::ptr list_alignments_referenced_in_site(new Schema::IfcSpatialReferenceSelect::list); + list_alignments_referenced_in_site->push(alignment); + auto rel_referenced_in_spatial_structure = new Schema::IfcRelReferencedInSpatialStructure(IfcParse::IfcGlobalId(), nullptr, boost::none, boost::none, list_alignments_referenced_in_site, site); + file.addEntity(rel_referenced_in_spatial_structure); + + std::ofstream ofs("FHWA_Bridge_Geometry_Alignment_Example.ifc"); + ofs << file; +} diff --git a/src/ifcgeom/ConversionSettings.cpp b/src/ifcgeom/ConversionSettings.cpp index e2fc14175d..36669c7971 100644 --- a/src/ifcgeom/ConversionSettings.cpp +++ b/src/ifcgeom/ConversionSettings.cpp @@ -37,3 +37,17 @@ std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, Ite } return in; } + +std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, PiecewiseStepMethod& ioo) { + std::string token; + in >> token; + boost::to_upper(token); + if (token == "MAXSTEPSIZE") { + ioo = MAXSTEPSIZE; + } else if (token == "MINSTEPS") { + ioo = MINSTEPS; + } else { + in.setstate(std::ios_base::failbit); + } + return in; +} diff --git a/src/ifcgeom/ConversionSettings.h b/src/ifcgeom/ConversionSettings.h index 18195f2f75..2a8e8115c5 100644 --- a/src/ifcgeom/ConversionSettings.h +++ b/src/ifcgeom/ConversionSettings.h @@ -300,12 +300,29 @@ namespace ifcopenshell { static constexpr const char* const description = "Overrides transparency of spaces in geometry output."; }; + enum PiecewiseStepMethod { + MAXSTEPSIZE, + MINSTEPS }; + + std::istream& operator>>(std::istream& in, PiecewiseStepMethod& ioo); + + struct PiecewiseStepType : public SettingBase { + static constexpr const char* const name = "piecewise-step-type"; + static constexpr const char* const description = "Indicates the method used for defining step size when evaluating piecewise curves. Provides interpretation of piecewise-step-param"; + static constexpr PiecewiseStepMethod defaultvalue = MAXSTEPSIZE; + }; + + struct PiecewiseStepParam : public SettingBase { + static constexpr const char* const name = "piecewise-step-param"; + static constexpr const char* const description = "Indicates the parameter value for defining step size when evaluating piecewise curves."; + static constexpr double defaultvalue = 0.5; // ceiling of this value is used when PiecewiseStepMethod is MinSteps + }; } template class IFC_GEOM_API SettingsContainer { public: - typedef boost::variant, IteratorOutputOptions> value_variant_t; + typedef boost::variant, IteratorOutputOptions, PiecewiseStepMethod> value_variant_t; private: settings_t settings; @@ -385,7 +402,7 @@ namespace ifcopenshell { }; class IFC_GEOM_API Settings : public SettingsContainer< - std::tuple + std::tuple > {}; } diff --git a/src/ifcgeom/mapping/IfcAxis2PlacementLinear.cpp b/src/ifcgeom/mapping/IfcAxis2PlacementLinear.cpp index 17ba88a031..69fb363b62 100644 --- a/src/ifcgeom/mapping/IfcAxis2PlacementLinear.cpp +++ b/src/ifcgeom/mapping/IfcAxis2PlacementLinear.cpp @@ -43,23 +43,27 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcAxis2PlacementLinear* inst) if (hasAxis) { taxonomy::direction3::ptr v = taxonomy::cast(map(inst->Axis())); axis = *v->components_; - } + } else { + // 8.9.3.4 IfcAxis2LinearPlacement does not specify the default when Axis is omitted + // When RefDirection is omitted, see comment below, it is taken to be tangent to the curve. + // To be consistent, Axis is taken to be orthogonal to RefDirection + axis = m->components().col(2).head<3>(); + } if (hasRef) { taxonomy::direction3::ptr v = taxonomy::cast(map(inst->RefDirection())); refDirection = *v->components_; } else { - // @todo: rb "If RefDirection is omitted, the direction is taken from the curve tangent at Location" + // 8.9.3.4 IfcAxis2LinearPlacement // https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/HTML/lexical/IfcAxis2PlacementLinear.htm - // Based on our email discussion, I'm using the perpendicular direction towards the right as viewed in the XY Plane for .RefDirection + // "If RefDirection is omitted, the direction is taken from the curve tangent at Location" // // When the PointByDistanceExpression Location is evaluated, it is evaluating the basis curve and returning // the matrix of orthogonal vectors that define the coordinate system at the point on curve as well as the point on curve // In other words, the m matrix has everything needed - refDirection = m->components().col(1).head<3>(); - - axis = m->components().col(2).head<3>(); + refDirection = m->components().col(0).head<3>(); + } return taxonomy::make(o, axis, refDirection); } diff --git a/src/ifcgeom/mapping/IfcCompositeCurve.cpp b/src/ifcgeom/mapping/IfcCompositeCurve.cpp index 7d73741a9c..be5e723785 100644 --- a/src/ifcgeom/mapping/IfcCompositeCurve.cpp +++ b/src/ifcgeom/mapping/IfcCompositeCurve.cpp @@ -23,7 +23,7 @@ using namespace ifcopenshell::geometry; taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCompositeCurve* inst) { auto loop = taxonomy::make(); - auto pwf = taxonomy::make(); + auto pwf = taxonomy::make(&settings_); #ifdef SCHEMA_HAS_IfcSegment // 4x3 diff --git a/src/ifcgeom/mapping/IfcCurveSegment.cpp b/src/ifcgeom/mapping/IfcCurveSegment.cpp index 1d46dba51b..db36841328 100644 --- a/src/ifcgeom/mapping/IfcCurveSegment.cpp +++ b/src/ifcgeom/mapping/IfcCurveSegment.cpp @@ -25,6 +25,8 @@ using namespace ifcopenshell::geometry; #include "../profile_helper.h" +#include + #include #include #include @@ -263,7 +265,7 @@ class linear_segment_geometry_adjuster : public segment_geometry_adjuster { // For now, the derivative of the curvature of the IfcCurve subtype is difficult to implement and example models from the IFC spec // always use IfcAxis2Placement3D with Axis and RefDirection specified, the basic interpolation is used, ignoring the IfcCurve type. // -// This implementation will be revised as the understanding of IfcSegmentedRefereneCurve improves. +// This implementation will be revised as the understanding of IfcSegmentedReferenceCurve improves. class cant_adjuster : public segment_geometry_adjuster { public: using segment_geometry_adjuster::segment_geometry_adjuster; @@ -274,17 +276,35 @@ class cant_adjuster : public segment_geometry_adjuster { auto& start_next = get_start_of_next_segment(); auto l = get_length(); - for (int i = 0; i < 4; i++) { - p.col(i) = start_this.col(i) + (start_next.col(i) - start_this.col(i)) * u / l; - if (i < 3) { - p.col(i).normalize(); - }; - } + // tilt angle of vector normal to cant at start of this and start of next segment + auto tilt_start_this = atan2(start_this.col(2)(2), start_this.col(2)(1)); + auto tilt_start_next = atan2(start_next.col(2)(2), start_next.col(2)(1)); - // when cant results are combined with the gradient curve - // the x-locate will be added which effective doubles them - // for this reason, set x location to 0 - p.col(3)(0) = 0; + // tilt angle of vector normal to cant at u assuming linear interpolation + // @todo: rb - rate of change of slope is related to curve type (such as clothoid or line) + // need to somehow account for that - it is important when tilt at start of next isn't provided + // because it defines how much tilt_start_this varies along the length + auto tilt = tilt_start_this + (tilt_start_next - tilt_start_this) * u / l; + + // populate Axis vector + p.col(2)(1) = cos(tilt); + p.col(2)(2) = sin(tilt); + + // populate Y vector + p.col(1)(1) = -p.col(2)(2); + p.col(1)(2) = p.col(2)(1); + + // use linear interpolation to compute elevation change due to cant + auto st = start_this.col(3)(1); + auto sn = start_next.col(3)(1); + auto slope = (sn - st) / l; + + // RefDirection.z is due to cant elevation change slope + p.col(0)(2) = slope; + p.col(0).normalize(); + + auto result = st + u * slope; + p.col(3)(1) = result; } protected: @@ -297,7 +317,7 @@ typedef boost::mpl::vector< , IfcSchema::IfcClothoid #endif #if defined SCHEMA_HAS_IfcSecondOrderPolynomialSpiral - , IfcSchema::IfcSecondOrderPolynomialSpiral + //, IfcSchema::IfcSecondOrderPolynomialSpiral // this isn't implemented yet, just some stubbed out dummy code #endif , IfcSchema::IfcPolyline , IfcSchema::IfcCircle @@ -366,7 +386,7 @@ class curve_segment_evaluator { } } - void set_spiral_function(mapping* mapping_, const IfcSchema::IfcSpiral* c, double s, std::function signX, std::function fnX, std::function signY, std::function fnY) { + void set_spiral_function(mapping* mapping_, const IfcSchema::IfcSpiral* c, double s, std::function signX, std::function fnX, std::function signY, std::function fnY, std::function fnSlope) { // determine the length of the spiral from the local origin to the end point auto sign_s = binary_sign(start_); auto sign_l = binary_sign(length_); @@ -384,7 +404,7 @@ class curve_segment_evaluator { auto segment_type = segment_type_; auto transformation_matrix = taxonomy::cast(mapping_->map(c->Position()))->ccomponents(); geometry_adjuster = std::make_shared(mapping_, segment_type_, inst_, next_inst_); - eval_ = [L, start, s, signX, fnX, signY, fnY, transformation_matrix, segment_type, geometry_adjuster = this->geometry_adjuster](double u) { + eval_ = [L, start, s, signX, fnX, signY, fnY, fnSlope, transformation_matrix, segment_type, geometry_adjuster = this->geometry_adjuster](double u) { u += start; @@ -402,11 +422,17 @@ class curve_segment_evaluator { // However, Dx and Dy are not normalized. Recall that slope = rise/run // If run = 1.0, then rise = Dy/Dx = fnY(u)/fnX(u) and l = sqrt((fnY(u)/fnX(u))^2 + 1.0^2) // The direction ratios are dx = 1.0/l and dy = (fnY/fnX)/l; - auto rise = fnY(u) / fnX(u); - auto run = 1.0; - auto l = sqrt(run * run + rise * rise); - auto dx = run / l; - auto dy = rise / l; + //auto fy = fnY(u); + //auto fx = fnX(u); + //auto rise = fy / fx; + //auto run = 1.0; + //auto l = sqrt(run * run + rise * rise); + //auto dx = run / l; + //auto dy = rise / l; + + auto slope = fnSlope(b); + auto dx = signX(u) * cos(slope); + auto dy = signY(u) * sin(slope); Eigen::Matrix4d m; if (segment_type == ST_HORIZONTAL) { @@ -507,8 +533,10 @@ class curve_segment_evaluator { auto sign_y = [A](double t) { return sign(t) == sign(A) ? 1.0 : -1.0; }; auto fn_x = [A, s](double t) -> double { return s * cos(PI * fabs(A) * t * t / (2 * fabs(A))); }; auto fn_y = [A, s](double t) -> double { return s * sin(PI * fabs(A) * t * t / (2 * fabs(A))); }; + //auto fn_slope = [A](double t) -> double { return sqrt(PI) * t * t / (2 * abs(A)); }; + auto fn_slope = [A, s](double t) -> double { return pow(t*s / A, 2) / 2; }; - set_spiral_function(mapping_, c, s, sign_x, fn_x, sign_y, fn_y); + set_spiral_function(mapping_, c, s, sign_x, fn_x, sign_y, fn_y, fn_slope); } #endif @@ -533,18 +561,19 @@ class curve_segment_evaluator { auto fn_x = [theta](double t)->double {return cos(theta(t)); }; auto fn_y = [theta](double t)->double {return sin(theta(t)); }; + auto fn_slope = [](double t)->double { return tan(t); }; double s = 1.0; // @todo: rb - this is supposed to be the curve length when the parametric value u = 1.0 - set_spiral_function(mapping_, c, s, sign_x, fn_x, sign_y, fn_y); + set_spiral_function(mapping_, c, s, sign_x, fn_x, sign_y, fn_y, fn_slope); } #endif void operator()(const IfcSchema::IfcCircle* c) { - auto R = c->Radius(); + auto R = c->Radius() * length_unit_; auto sign_l = sign(length_); - auto start = start_; + auto start_angle = start_/R; auto transformation_matrix = taxonomy::cast(mapping_->map(c->Position()))->ccomponents(); @@ -552,9 +581,9 @@ class curve_segment_evaluator { geometry_adjuster = std::make_shared(mapping_, segment_type_, inst_, next_inst_); - eval_ = [R, start, sign_l, transformation_matrix, segment_type, geometry_adjuster = this->geometry_adjuster](double u) + eval_ = [R, start_angle, sign_l, transformation_matrix, segment_type, geometry_adjuster = this->geometry_adjuster](double u) { - auto angle = start + sign_l * u / R; + auto angle = start_angle + sign_l * u / R; auto dx = cos(angle); auto dy = sin(angle); @@ -565,15 +594,15 @@ class curve_segment_evaluator { Eigen::Matrix4d m = Eigen::Matrix4d::Identity(); if (segment_type == ST_HORIZONTAL) { // rotate about the Z-axis - m.col(0) = Eigen::Vector4d(dx, dy, 0, 0); // vector tangent to the curve, in the direction of the curve - m.col(1) = Eigen::Vector4d(-dy, dx, 0, 0); // vector perpendicular to the curve, towards the left when looking from start to end along the curve (this is used for IfcAxis2PlacementLinear.RefDirection when it is not provided) - m.col(2) = Eigen::Vector4d(0, 0, 1.0, 0); // cross product of x and y and will always be up (this is used for IfcAxis2PlacementLinear.Axis when it is not provided) + m.col(0) = Eigen::Vector4d(-dy, dx, 0, 0); // vector tangent to the curve, in the direction of the curve + m.col(1) = Eigen::Vector4d(-sign_l * dx, -sign_l * dy, 0, 0); // vector perpendicular to the curve, towards the left when looking from start to end along the curve (this is used for IfcAxis2PlacementLinear.RefDirection when it is not provided) + m.col(2) = Eigen::Vector4d(0, 0, 1.0, 0); // cross product of x and y and will always be up (this is used for IfcAxis2PlacementLinear.Axis when it is not provided) m.col(3) = Eigen::Vector4d(x, y, 0.0, 1.0); } else if (segment_type == ST_VERTICAL) { // rotate about the Y-axis (slope along u is dx, slope vertically is dy, vertical position is y) - m.col(0) = Eigen::Vector4d(dx, 0, dy, 0); + m.col(0) = Eigen::Vector4d(-dy, 0, dx, 0); m.col(1) = Eigen::Vector4d(0, 1, 0, 0); - m.col(2) = Eigen::Vector4d(-dy, 0, dx, 0); + m.col(2) = Eigen::Vector4d(-dx, 0, -dy, 0); m.col(3) = Eigen::Vector4d(0, 0, y, 1.0); // y is an elevation so store it as z } else if (segment_type == ST_CANT) { Logger::Warning(std::runtime_error("Use of IfcCircle for cant is not supported")); @@ -700,12 +729,24 @@ class curve_segment_evaluator { auto s = l->Pnt(); auto c = s->Coordinates(); auto v = l->Dir(); + + // 8.9.3.75 IfcVector https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/HTML/lexical/IfcVector.htm + // 8.9.3.30 IfcDirection https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/HTML/lexical/IfcDirection.htm + // "The IfcDirection does not imply a vector length, and the direction ratios does not have to be normalized." + // + // Therefore, the direction ratios need to be normalized to compute points on the line. Magnitude is not used + // because it relates to the parameterization of the line, which isn't currently done for IfcCurveSegment auto dr = v->Orientation()->DirectionRatios(); - auto m = v->Magnitude(); - auto px = c[0]; - auto py = c[1]; - auto dx = dr[0] / m; - auto dy = dr[1] / m; + + // normalize the direction ratios + double m_squared = std::inner_product(dr.begin(), dr.end(), dr.begin(), 0.0); + double m = sqrt(m_squared); + std::for_each(dr.begin(), dr.end(), [m](auto& d) { return d / m; }); + auto dx = dr[0]; + auto dy = dr[1]; + + auto px = c[0] * length_unit_; + auto py = c[1] * length_unit_; geometry_adjuster = std::make_shared(mapping_, segment_type_, inst_, next_inst_); if (segment_type_ == ST_HORIZONTAL) { @@ -765,28 +806,35 @@ class curve_segment_evaluator { if (!coeffZ.empty()) Logger::Warning("Expected IfcPolynomialCurve.CoefficientsZ to be undefined for alignment geometry. Coefficients ignored.", p); + + auto transformation_matrix = taxonomy::cast(mapping_->map(p->Position()))->ccomponents(); auto segment_type = segment_type_; + auto length_unit = length_unit_; geometry_adjuster = std::make_shared(mapping_, segment_type_, inst_, next_inst_); - eval_ = [coeffX, coeffY, transformation_matrix, segment_type, geometry_adjuster = this->geometry_adjuster](double u) { + eval_ = [coeffX, coeffY, transformation_matrix, segment_type, length_unit, geometry_adjuster = this->geometry_adjuster](double u) { std::array*, 2> coefficients{&coeffX, &coeffY}; - std::array position{0.0, 0.0}; + std::array position{0.0, 0.0}; // = SUM(coeff*u^pos) std::array slope{0.0, 0.0}; // slope is derivative of the curve = SUM( coeff*pos*u^(pos-1) ) for (int i = 0; i < 2; i++) { + auto length_conversion = length_unit; auto begin = coefficients[i]->cbegin(); auto end = coefficients[i]->cend(); - for (auto iter = begin; iter != end; iter++) { + for (auto iter = begin; iter != end; iter++) { auto exp = std::distance(begin, iter); - position[i] += (*iter) * pow(u, exp); + auto coeff = (*iter)*length_conversion; + position[i] += coeff* pow(u, exp); if (iter != begin) { - slope[i] += (*iter) * exp * pow(u, exp - 1); + slope[i] += coeff * exp * pow(u, exp - 1); } - } + + length_conversion /= length_unit; + } } auto x = position[0]; @@ -804,9 +852,9 @@ class curve_segment_evaluator { m.col(3) = Eigen::Vector4d(x, y, 0.0, 1.0); } else if (segment_type == ST_VERTICAL) { // rotate about the Y-axis (slope along u is dx, slope vertically is dy, vertical position is y) - m.col(0) = Eigen::Vector4d(dx, 0, -dy, 0); + m.col(0) = Eigen::Vector4d(dx, 0, dy, 0); m.col(1) = Eigen::Vector4d(0, 1, 0, 0); - m.col(2) = Eigen::Vector4d(dy, 0, dx, 0); + m.col(2) = Eigen::Vector4d(-dy, 0, dx, 0); m.col(3) = Eigen::Vector4d(0, 0, y, 1.0); // y is an elevation so store it as z } else if (segment_type == ST_CANT) { Logger::Warning(std::runtime_error("Use of IfcPolynomialCurve for cant is not supported")); @@ -894,7 +942,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCurveSegment* inst) { auto length = fabs(cse.length()); // @todo it might be suboptimal that we no longer have the spans now - auto pwf = taxonomy::make(); + auto pwf = taxonomy::make(&settings_); pwf->spans.push_back({ length, fn }); pwf->instance = inst; return pwf; diff --git a/src/ifcgeom/mapping/IfcGradientCurve.cpp b/src/ifcgeom/mapping/IfcGradientCurve.cpp index a01d42f488..56c42c69be 100644 --- a/src/ifcgeom/mapping/IfcGradientCurve.cpp +++ b/src/ifcgeom/mapping/IfcGradientCurve.cpp @@ -28,7 +28,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcGradientCurve* inst) { Logger::Warning("Expected IfcGradientCurve.BaseCurve to be IfcCompositeCurve", inst); // CT 4.1.7.1.1.2 auto horizontal = taxonomy::cast(map(inst->BaseCurve())); - auto vertical = taxonomy::make(); + auto vertical = taxonomy::make(&settings_); auto segments = inst->Segments(); @@ -73,7 +73,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcGradientCurve* inst) { } } - auto pwf = taxonomy::make(); + auto pwf = taxonomy::make(&settings_); pwf->spans.emplace_back( min_length, composition ); pwf->instance = inst; return pwf; diff --git a/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp b/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp new file mode 100644 index 0000000000..390db1ee71 --- /dev/null +++ b/src/ifcgeom/mapping/IfcOffsetCurveByDistance.cpp @@ -0,0 +1,162 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell 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 * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copz of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +// ifc4x1 +//#define SCHEMA_IfcOffsetCurveByDistances_HAS_OffsetValues +//#define SCHEMA_IfcOffsetCurveByDistances_HAS_Tag +//#define SCHEMA_IfcOffsetCurveByDistances_Tag_IS_OPTIONAL + +#ifdef SCHEMA_HAS_IfcOffsetCurveByDistances + +taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst) { + auto offset_values = inst->OffsetValues(); + if (offset_values->size() == 0) { + Logger::Error("IfcOffsetCurveByDistances must have at least one offset value"); + } + + auto basis_curve = inst->BasisCurve(); + + auto first_offset_value = *(offset_values->begin()); + + // todo@ rb - basis_curve might not be piecewise - other valid types are IfcOffsetCurveByDistances, IfcPolyline and IfcIndexedPolyCurve + // Is there a more generic type that can be evaluated at "u"? + auto basis = taxonomy::cast(map(basis_curve)); + double basis_curve_length = 0; + for (auto& s : basis->spans) { + basis_curve_length += s.first; + } + + auto offsets = taxonomy::make(&settings_); + +#if defined SCHEMA_HAS_IfcDistanceExpression + double first_distance = first_offset_value->DistanceAlong(); +#else + double first_distance = *first_offset_value->DistanceAlong()->as(); +#endif + + if (first_distance < 0.0) { + Logger::Warning("IfcOffsetCurveByDistance first offset value is before the start of the curve."); + } + + if(0.0 < first_distance) + { + // First offset is defined after the start of the curve so the lateral and vertical offsets + // implicitly continue with the same value towards the start of the basis curve + double py = first_offset_value->OffsetLateral().get_value_or(0.0); + double pz = first_offset_value->OffsetVertical().get_value_or(0.0); + py *= length_unit_; + pz *= length_unit_; + + auto fn = [py, pz](double u) -> Eigen::Matrix4d { + Eigen::Matrix4d m = Eigen::Matrix4d::Identity(); + m.col(3)(1) = py; + m.col(3)(2) = pz; + return m; }; + offsets->spans.push_back({first_distance, fn}); + } + + auto iter = offset_values->begin(); + auto next = std::next(iter); + auto prev = std::prev(next); + auto end = offset_values->end(); + for (; next != end; prev++, next++) { +#if defined SCHEMA_HAS_IfcPointByDistanceExpression + if ((*prev)->BasisCurve() != basis_curve || (*next)->BasisCurve() != basis_curve) { + Logger::Error("All offsets from a IfcOffsetCurveByDistances must refer to the same BasisCurve"); + } +#endif + +#if defined SCHEMA_HAS_IfcDistanceExpression + double dn = (*next)->DistanceAlong(); + double dp = (*prev)->DistanceAlong(); +#else + double dn = *(*next)->DistanceAlong()->as(); + double dp = *(*prev)->DistanceAlong()->as(); +#endif + if ((dp < 0.0 || basis_curve_length < dp) + or + (dn < 0.0 || basis_curve_length < dn)) + { + Logger::Warning("IfcOffsetCurveByDistance offset value is out of bounds."); + continue; + } + + double l = (dn - dp)*length_unit_; + double yn = (*next)->OffsetLateral().get_value_or(0.0) * length_unit_; + double yp = (*prev)->OffsetLateral().get_value_or(0.0) * length_unit_; + double zn = (*next)->OffsetVertical().get_value_or(0.0) * length_unit_; + double zp = (*prev)->OffsetVertical().get_value_or(0.0) * length_unit_; + + auto fn = [yp, yn, zp, zn, l](double u) -> Eigen::Matrix4d { + Eigen::Matrix4d m = Eigen::Matrix4d::Identity(); + m.col(3)(1) = (l == 0.0 ? yp : (yp + (yn - yp) * u / l)); + m.col(3)(2) = (l == 0.0 ? zp : (zp + (zn - zp) * u / l)); + return m; + }; + offsets->spans.push_back({l, fn}); + } + + // at this point, next == end and prev == end-1 + #if defined SCHEMA_HAS_IfcDistanceExpression + double last_distance = (*prev)->DistanceAlong() * length_unit_; + #else + double last_distance = *(*prev)->DistanceAlong()->as() * length_unit_; + #endif + + if (basis_curve_length < last_distance) { + Logger::Warning("IfcOffsetCurveByDistance last offset value is after the end of the curve."); + } + + if (last_distance < basis_curve_length) { + // Last offset is defined before the end of the curve so the lateral and vertical offsets + // implicitly continue with the same value towards the end of the basis curve + double py = (*prev)->OffsetLateral().get_value_or(0.0); + double pz = (*prev)->OffsetVertical().get_value_or(0.0); + py *= length_unit_; + pz *= length_unit_; + double l = basis_curve_length - last_distance; + auto fn = [py, pz](double u) -> Eigen::Matrix4d { + Eigen::Matrix4d m = Eigen::Matrix4d::Identity(); + m.col(3)(1) = py; + m.col(3)(2) = pz; + return m; }; + + offsets->spans.push_back({l, fn}); + } + + auto composition = [basis, offsets](double u)->Eigen::Matrix4d { + auto p = basis->evaluate(u); + auto offset = offsets->evaluate(u); + Eigen::Matrix4d m = p * offset; + return m; + }; + + // current implementation assumes that offsets is equal to the full length of basis curve + // this may change depending on decisions in the bSI-IF + auto pwf = taxonomy::make(&settings_); + pwf->spans.emplace_back( basis_curve_length, composition ); + pwf->instance = inst; + return pwf; +} + +#endif \ No newline at end of file diff --git a/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp b/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp index 50383de7ae..095b41e9c9 100644 --- a/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp +++ b/src/ifcgeom/mapping/IfcSegmentedReferenceCurve.cpp @@ -28,7 +28,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve* ins Logger::Warning("Expected IfcSegmentedReferenceCurve.BaseCurve to be IfcGradient", inst); // CT 4.1.7.1.1.3 auto gradient = taxonomy::cast(map(inst->BaseCurve())); - auto cant = taxonomy::make(); + auto cant = taxonomy::make(&settings_); auto segments = inst->Segments(); @@ -50,12 +50,13 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve* ins } auto composition = [gradient, cant](double u)->Eigen::Matrix4d { - auto xyz = gradient->evaluate(u); + auto g = gradient->evaluate(u); auto c = cant->evaluate(u); - c.col(3)(0) = 0; - std::swap(c.col(3)(1),c.col(3)(2)); + + std::swap(c.col(3)(1), c.col(3)(2)); + Eigen::Matrix4d m; - m = xyz * c; + m = g * c; return m; }; @@ -75,7 +76,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve* ins } } - auto pwf = taxonomy::make(); + auto pwf = taxonomy::make(&settings_); pwf->spans.emplace_back( min_length, composition ); pwf->instance = inst; return pwf; diff --git a/src/ifcgeom/mapping/mapping.i b/src/ifcgeom/mapping/mapping.i index 6466904093..3173368d90 100644 --- a/src/ifcgeom/mapping/mapping.i +++ b/src/ifcgeom/mapping/mapping.i @@ -120,6 +120,9 @@ BIND(IfcSegmentedReferenceCurve); BIND(IfcGradientCurve); #endif BIND(IfcCompositeCurve); +#ifdef SCHEMA_HAS_IfcOffsetCurveByDistances +BIND(IfcOffsetCurveByDistances) +#endif BIND(IfcTrimmedCurve); BIND(IfcArbitraryOpenProfileDef); #ifdef SCHEMA_HAS_IfcIndexedPolyCurve diff --git a/src/ifcgeom/taxonomy.cpp b/src/ifcgeom/taxonomy.cpp index 432d2d2c7c..8d7b05d725 100644 --- a/src/ifcgeom/taxonomy.cpp +++ b/src/ifcgeom/taxonomy.cpp @@ -460,11 +460,20 @@ ifcopenshell::geometry::taxonomy::item::ptr ifcopenshell::geometry::taxonomy::pi length += s.first; std::vector polygon; + + auto param_type = settings_ ? settings_->get().get() : ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE; + auto param = settings_ ? settings_->get().get() : 0.5; + int num_steps = 0; + if (param_type == ifcopenshell::geometry::settings::PiecewiseStepMethod::MAXSTEPSIZE) { + // parameter is max step size + num_steps = (int)std::ceil(length / param); + } else { + // parameter is minimum number of steps + num_steps = (int)std::ceil(param); + } + auto resolution = length / num_steps; - static const double target_resolution = 0.5; - int num_steps = (int)std::ceil(length / target_resolution); - auto resolution = length / num_steps; - for (int i = 0; i <= num_steps; ++i) { + for (int i = 0; i <= num_steps; ++i) { auto u = resolution * i; Eigen::Matrix4d m = evaluate(u); polygon.push_back(taxonomy::make(m.col(3)(0), m.col(3)(1), m.col(3)(2))); diff --git a/src/ifcgeom/taxonomy.h b/src/ifcgeom/taxonomy.h index 8d1358351c..b0392d73ce 100644 --- a/src/ifcgeom/taxonomy.h +++ b/src/ifcgeom/taxonomy.h @@ -3,6 +3,8 @@ #include "../ifcparse/IfcBaseClass.h" +#include "ConversionSettings.h" + #include #include @@ -127,6 +129,7 @@ typedef item const* ptr; struct implicit_item : public item { DECLARE_PTR(implicit_item) + using item::item; virtual item::ptr evaluate() const = 0; }; @@ -134,6 +137,13 @@ typedef item const* ptr; struct piecewise_function : public implicit_item { DECLARE_PTR(piecewise_function) + piecewise_function(const IfcUtil::IfcBaseInterface* instance = nullptr) : implicit_item(instance){}; + piecewise_function(ifcopenshell::geometry::Settings* settings) : settings_(settings){}; + piecewise_function(piecewise_function&&) = default; + piecewise_function(const piecewise_function&) = default; + + ifcopenshell::geometry::Settings* settings_ = nullptr; + // length of span, function to evaluate span std::vector>> spans;