Skip to content

Commit

Permalink
Improved viewer application. Added reading styles of boolean operands
Browse files Browse the repository at this point in the history
  • Loading branch information
ifcquery committed Jan 26, 2024
1 parent 6188708 commit 118636e
Show file tree
Hide file tree
Showing 23 changed files with 287 additions and 2,418 deletions.
1 change: 0 additions & 1 deletion IfcPlusPlus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ TARGET_INCLUDE_DIRECTORIES(IfcPlusPlus
${IFCPP_SOURCE_DIR}/IfcPlusPlus/src/external/glm
)


set_target_properties(IfcPlusPlus PROPERTIES DEBUG_POSTFIX "d")
set_target_properties(IfcPlusPlus PROPERTIES CXX_STANDARD 17)

Expand Down
261 changes: 132 additions & 129 deletions IfcPlusPlus/src/ifcpp/geometry/CurveConverter.h

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions IfcPlusPlus/src/ifcpp/geometry/GeomUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,10 @@ namespace GeomUtils
inline bool isPolygonSelfIntersecting(const std::vector<vec2>& polygon, double eps)
{
size_t numPoints = polygon.size();
if (numPoints < 4)
{
return false;
}
for (int ii = 0; ii < numPoints; ++ii)
{
const vec2& p1 = polygon[ii];
Expand Down Expand Up @@ -1456,10 +1460,10 @@ namespace GeomUtils
vec3& point = meshset->vertex_storage[i].v;
point = point + pos;
}
for (size_t i = 0; i < meshset->meshes.size(); ++i)
{
meshset->meshes[i]->recalc(eps);
}
//for (size_t i = 0; i < meshset->meshes.size(); ++i)
//{
// meshset->meshes[i]->recalc(eps);
//}
}
inline void applyTransform(shared_ptr<carve::mesh::MeshSet<3> >& meshset, const carve::math::Matrix& matrix, double eps)
{
Expand Down
1 change: 1 addition & 0 deletions IfcPlusPlus/src/ifcpp/geometry/GeometryConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class GeometryConverter : public StatusCallback
shared_ptr<BuildingModel>& getBuildingModel() { return m_ifc_model; }
shared_ptr<RepresentationConverter>& getRepresentationConverter() { return m_representation_converter; }
shared_ptr<GeometrySettings>& getGeomSettings() { return m_geom_settings; }
void setGeomSettings(shared_ptr<GeometrySettings>& settings) { m_geom_settings = settings; }
std::map<std::string, shared_ptr<ProductShapeData> >& getShapeInputData() { return m_product_shape_data; }
std::map<std::string, shared_ptr<BuildingObject> >& getObjectsOutsideSpatialStructure() { return m_map_outside_spatial_structure; }
bool m_clear_memory_immedeately = true;
Expand Down
7 changes: 3 additions & 4 deletions IfcPlusPlus/src/ifcpp/geometry/PointConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ class PointConverter : public StatusCallback
while (angle < -2.0 * M_PI) { angle += 2.0 * M_PI; }
vec3 circlePoint = carve::geom::VECTOR(radius * cos(angle), radius * sin(angle), 0);
circlePoint = circlePosition * circlePoint;
double distance2 = (trimPoint - circlePoint).length2();
return distance2;
double distance = (trimPoint - circlePoint).length();
return distance;
}

//\brief: returns the corresponding angle in radian. angle 0 is on the positive x-axis.
Expand Down Expand Up @@ -328,8 +328,7 @@ class PointConverter : public StatusCallback
}
}

vec3 translation = circlePosition * carve::geom::VECTOR(0, 0, 0);
if ((translation - circleCenter).length() > eps * 10)
if ((circleCenter3D - circleCenter).length() > eps * 10)
{
std::cout << __FUNCTION__ << ": circle center not at (0,0,0)" << std::endl;
}
Expand Down
70 changes: 35 additions & 35 deletions IfcPlusPlus/src/ifcpp/geometry/RepresentationConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class RepresentationConverter : public StatusCallback
m_curve_converter = shared_ptr<CurveConverter>( new CurveConverter( m_geom_settings, m_placement_converter, m_point_converter, m_spline_converter ) );
m_profile_cache = shared_ptr<ProfileCache>( new ProfileCache( m_curve_converter, m_spline_converter ) );
m_face_converter = shared_ptr<FaceConverter>( new FaceConverter( m_geom_settings, m_unit_converter, m_curve_converter, m_spline_converter, m_sweeper, m_profile_cache ) );
m_solid_converter = shared_ptr<SolidModelConverter>( new SolidModelConverter( m_geom_settings, m_point_converter, m_curve_converter, m_face_converter, m_profile_cache, m_sweeper ) );
m_solid_converter = shared_ptr<SolidModelConverter>( new SolidModelConverter( m_geom_settings, m_point_converter, m_curve_converter, m_face_converter, m_profile_cache, m_sweeper, m_styles_converter ) );

// this redirects the callback messages from all converters to RepresentationConverter's callback
m_styles_converter->setMessageTarget( this );
Expand Down Expand Up @@ -139,59 +139,59 @@ class RepresentationConverter : public StatusCallback
m_face_converter->m_unit_converter = unit_converter;
}

void convertRepresentationStyle( const shared_ptr<IfcRepresentationItem>& representation_item, std::vector<shared_ptr<StyleData> >& vec_style_data )
//void convertRepresentationStyle( const shared_ptr<IfcRepresentationItem>& representation_item, std::vector<shared_ptr<StyleData> >& vec_style_data )
//{
// std::vector<weak_ptr<IfcStyledItem> >& vec_StyledByItem_inverse = representation_item->m_StyledByItem_inverse;
// for( size_t i = 0; i < vec_StyledByItem_inverse.size(); ++i )
// {
// weak_ptr<IfcStyledItem> styled_item_weak = vec_StyledByItem_inverse[i];
// shared_ptr<IfcStyledItem> styled_item = shared_ptr<IfcStyledItem>( styled_item_weak );
// m_styles_converter->convertIfcStyledItem( styled_item, vec_style_data );
// }
//}

void convertIfcRepresentation( const shared_ptr<IfcRepresentation>& ifcRepresentation, shared_ptr<ItemShapeData>& representationData )
{
std::vector<weak_ptr<IfcStyledItem> >& vec_StyledByItem_inverse = representation_item->m_StyledByItem_inverse;
for( size_t i = 0; i < vec_StyledByItem_inverse.size(); ++i )
{
weak_ptr<IfcStyledItem> styled_item_weak = vec_StyledByItem_inverse[i];
shared_ptr<IfcStyledItem> styled_item = shared_ptr<IfcStyledItem>( styled_item_weak );
m_styles_converter->convertIfcStyledItem( styled_item, vec_style_data );
}
}
representationData->m_ifc_representation = ifcRepresentation;

void convertIfcRepresentation( const shared_ptr<IfcRepresentation>& ifc_representation, shared_ptr<ItemShapeData>& representation_data )
{
representation_data->m_ifc_representation = ifc_representation;

for( const shared_ptr<IfcRepresentationItem>& representation_item : ifc_representation->m_Items )
for( const shared_ptr<IfcRepresentationItem>& representationItem : ifcRepresentation->m_Items )
{
//ENTITY IfcRepresentationItem ABSTRACT SUPERTYPE OF(ONEOF(IfcGeometricRepresentationItem, IfcMappedItem, IfcStyledItem, IfcTopologicalRepresentationItem));
shared_ptr<IfcGeometricRepresentationItem> geom_item = dynamic_pointer_cast<IfcGeometricRepresentationItem>( representation_item );
if( geom_item )
shared_ptr<IfcGeometricRepresentationItem> geomItem = dynamic_pointer_cast<IfcGeometricRepresentationItem>( representationItem );
if( geomItem )
{
shared_ptr<ItemShapeData> geom_item_data( new ItemShapeData() );
shared_ptr<ItemShapeData> geomItemData( new ItemShapeData() );

try
{
convertIfcGeometricRepresentationItem( geom_item, geom_item_data );
representation_data->addGeometricChildItem(geom_item_data, representation_data);
convertIfcGeometricRepresentationItem( geomItem, geomItemData );
representationData->addGeometricChildItem(geomItemData, representationData);
}
catch( BuildingException& e )
{
messageCallback( e.what(), StatusCallback::MESSAGE_TYPE_ERROR, "", representation_item.get() );
messageCallback( e.what(), StatusCallback::MESSAGE_TYPE_ERROR, "", representationItem.get() );
}
catch( std::exception& e )
{
messageCallback( e.what(), StatusCallback::MESSAGE_TYPE_ERROR, __FUNC__, representation_item.get() );
messageCallback( e.what(), StatusCallback::MESSAGE_TYPE_ERROR, __FUNC__, representationItem.get() );
}

continue;
}

shared_ptr<IfcMappedItem> mapped_item = dynamic_pointer_cast<IfcMappedItem>( representation_item );
shared_ptr<IfcMappedItem> mapped_item = dynamic_pointer_cast<IfcMappedItem>( representationItem );
if( mapped_item )
{
shared_ptr<IfcRepresentationMap> map_source = mapped_item->m_MappingSource;
if( !map_source )
{
messageCallback( "MappingSource not valid", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representation_item.get() );
messageCallback( "MappingSource not valid", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representationItem.get() );
continue;
}
shared_ptr<IfcRepresentation> mapped_representation = map_source->m_MappedRepresentation;
if( !mapped_representation )
{
messageCallback( "MappingSource.MappedRepresentation not valid", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representation_item.get() );
messageCallback( "MappingSource.MappedRepresentation not valid", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representationItem.get() );
continue;
}

Expand Down Expand Up @@ -237,7 +237,7 @@ class RepresentationConverter : public StatusCallback
if( m_geom_settings->handleStyledItems() )
{
std::vector<shared_ptr<StyleData> > vec_style_data;
convertRepresentationStyle( representation_item, vec_style_data );
m_styles_converter->convertRepresentationStyle( representationItem, vec_style_data );

if( vec_style_data.size() > 0 )
{
Expand All @@ -258,32 +258,32 @@ class RepresentationConverter : public StatusCallback
double eps = m_geom_settings->getEpsilonMergePoints();
mapped_input_data->applyTransformToItem(mapped_pos, eps, false);
}
representation_data->addGeometricChildItem( mapped_input_data, representation_data );
representationData->addGeometricChildItem( mapped_input_data, representationData );
continue;
}

shared_ptr<IfcStyledItem> styled_item = dynamic_pointer_cast<IfcStyledItem>( representation_item );
shared_ptr<IfcStyledItem> styled_item = dynamic_pointer_cast<IfcStyledItem>( representationItem );
if( styled_item )
{
continue;
}

shared_ptr<IfcTopologicalRepresentationItem> topo_item = dynamic_pointer_cast<IfcTopologicalRepresentationItem>( representation_item );
shared_ptr<IfcTopologicalRepresentationItem> topo_item = dynamic_pointer_cast<IfcTopologicalRepresentationItem>( representationItem );
if( topo_item )
{
shared_ptr<ItemShapeData> topological_item_data( new ItemShapeData() );
representation_data->addGeometricChildItem( topological_item_data, representation_data );
representationData->addGeometricChildItem( topological_item_data, representationData );
//topological_item_data->m_ifc_representation_item = topo_item;
convertTopologicalRepresentationItem(topo_item, topological_item_data);
continue;
}

messageCallback( "unhandled representation", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representation_item.get() );
messageCallback( "unhandled representation", StatusCallback::MESSAGE_TYPE_WARNING, __FUNC__, representationItem.get() );
}

if( m_geom_settings->handleLayerAssignments() )
{
std::vector<weak_ptr<IfcPresentationLayerAssignment> >& vec_layer_assignments_inverse = ifc_representation->m_LayerAssignments_inverse;
std::vector<weak_ptr<IfcPresentationLayerAssignment> >& vec_layer_assignments_inverse = ifcRepresentation->m_LayerAssignments_inverse;
for( size_t ii = 0; ii < vec_layer_assignments_inverse.size(); ++ii )
{
weak_ptr<IfcPresentationLayerAssignment>& layer_assignment_weak = vec_layer_assignments_inverse[ii];
Expand Down Expand Up @@ -314,7 +314,7 @@ class RepresentationConverter : public StatusCallback
m_styles_converter->convertIfcPresentationStyle( presentation_style, style_data );
if( style_data )
{
representation_data->addStyle( style_data );
representationData->addStyle( style_data );
}
}
}
Expand Down Expand Up @@ -347,7 +347,7 @@ class RepresentationConverter : public StatusCallback
if( m_geom_settings->handleStyledItems() )
{
std::vector<shared_ptr<StyleData> > vec_style_data;
convertRepresentationStyle( geom_item, vec_style_data );
m_styles_converter->convertRepresentationStyle( geom_item, vec_style_data );
for (auto& style : vec_style_data)
{
item_data->addStyle(style);
Expand All @@ -373,7 +373,7 @@ class RepresentationConverter : public StatusCallback
m_solid_converter->convertIfcBooleanResult( boolean_result, item_data );
return;
}

shared_ptr<IfcSolidModel> solid_model = dynamic_pointer_cast<IfcSolidModel>( geom_item );
if( solid_model )
{
Expand Down
23 changes: 21 additions & 2 deletions IfcPlusPlus/src/ifcpp/geometry/SolidModelConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ class SolidModelConverter : public StatusCallback
shared_ptr<CurveConverter> m_curve_converter;
shared_ptr<FaceConverter> m_face_converter;
shared_ptr<ProfileCache> m_profile_cache;
shared_ptr<StylesConverter> m_styles_converter;
shared_ptr<Sweeper> m_sweeper;

SolidModelConverter( shared_ptr<GeometrySettings>& gs, shared_ptr<PointConverter>& pc, shared_ptr<CurveConverter>& cc,
shared_ptr<FaceConverter>& fc, shared_ptr<ProfileCache>& pcache, shared_ptr<Sweeper>& sw )
: m_geom_settings( gs ), m_point_converter( pc ), m_curve_converter( cc ), m_face_converter( fc ), m_profile_cache( pcache ), m_sweeper( sw )
shared_ptr<FaceConverter>& fc, shared_ptr<ProfileCache>& pcache, shared_ptr<Sweeper>& sw, shared_ptr<StylesConverter>& styles_converter )
: m_geom_settings( gs ), m_point_converter( pc ), m_curve_converter( cc ), m_face_converter( fc ), m_profile_cache( pcache ),
m_sweeper( sw ), m_styles_converter(styles_converter)
{
}

Expand Down Expand Up @@ -863,6 +865,9 @@ class SolidModelConverter : public StatusCallback
// now copy processed first operands to result input data
std::copy( first_operand_data->m_meshsets.begin(), first_operand_data->m_meshsets.end(), std::back_inserter( item_data->m_meshsets ) );

// copy also styles from operands, if any
std::copy(first_operand_data->m_vec_styles.begin(), first_operand_data->m_vec_styles.end(), std::back_inserter(item_data->m_vec_styles));

shared_ptr<IfcBooleanClippingResult> boolean_clipping_result = dynamic_pointer_cast<IfcBooleanClippingResult>( bool_result );
if( boolean_clipping_result )
{
Expand Down Expand Up @@ -1739,6 +1744,20 @@ class SolidModelConverter : public StatusCallback

void convertIfcBooleanOperand( const shared_ptr<IfcBooleanOperand>& operand_select, shared_ptr<ItemShapeData>& item_data, const shared_ptr<ItemShapeData>& other_operand )
{
if (m_geom_settings->handleStyledItems())
{
shared_ptr<IfcRepresentationItem> representationItem = dynamic_pointer_cast<IfcRepresentationItem>(operand_select);
if (representationItem)
{
std::vector<shared_ptr<StyleData> > vec_style_data;
m_styles_converter->convertRepresentationStyle(representationItem, vec_style_data);
for (auto& style : vec_style_data)
{
item_data->addStyle(style);
}
}
}

// TYPE IfcBooleanOperand = SELECT (IfcBooleanResult ,IfcCsgPrimitive3D ,IfcHalfSpaceSolid ,IfcSolidModel ,IfcTessellatedFaceSet);
shared_ptr<IfcSolidModel> solid_model = dynamic_pointer_cast<IfcSolidModel>( operand_select );
if( solid_model )
Expand Down
13 changes: 12 additions & 1 deletion IfcPlusPlus/src/ifcpp/geometry/StylesConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,17 @@ class StylesConverter : public StatusCallback
}
}

void convertRepresentationStyle(const shared_ptr<IfcRepresentationItem>& representation_item, std::vector<shared_ptr<StyleData> >& vec_style_data)
{
std::vector<weak_ptr<IfcStyledItem> >& vec_StyledByItem_inverse = representation_item->m_StyledByItem_inverse;
for (size_t i = 0; i < vec_StyledByItem_inverse.size(); ++i)
{
weak_ptr<IfcStyledItem> styled_item_weak = vec_StyledByItem_inverse[i];
shared_ptr<IfcStyledItem> styled_item = shared_ptr<IfcStyledItem>(styled_item_weak);
convertIfcStyledItem(styled_item, vec_style_data);
}
}

void convertIfcStyledItem(weak_ptr<IfcStyledItem> styled_item_weak, std::vector<shared_ptr<StyleData> >& vec_style_data)
{
if (styled_item_weak.expired())
Expand All @@ -750,7 +761,7 @@ class StylesConverter : public StatusCallback
const int style_id = styled_item->m_tag;

{
std::lock_guard<std::mutex> lock(m_writelock_styles_converter);
//std::lock_guard<std::mutex> lock(m_writelock_styles_converter);
auto it_find_existing_style = m_map_ifc_styles.find(style_id);
if (it_find_existing_style != m_map_ifc_styles.end())
{
Expand Down
24 changes: 12 additions & 12 deletions examples/SimpleViewerExampleQt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ ENDIF(NOT WIN32)
message(STATUS "Qt6 is used")

IF(DEFINED ENV{QT_DIR})
set(Qt5Core_DIR "$ENV{QT_DIR}/lib/cmake/Qt6Core")
set(Qt5_DIR "$ENV{QT_DIR}/lib/cmake/Qt6")
set(Qt6Core_DIR "$ENV{QT_DIR}/lib/cmake/Qt6Core")
set(Qt6_DIR "$ENV{QT_DIR}/lib/cmake/Qt6")
set(QT_QMAKE_EXECUTABLE "$ENV{QT_DIR}/bin/qmake.exe")
ENDIF()

FIND_PACKAGE(Qt5Core REQUIRED)
FIND_PACKAGE(Qt5Widgets REQUIRED)
FIND_PACKAGE(Qt5OpenGL REQUIRED)
FIND_PACKAGE(Qt6Core REQUIRED)
FIND_PACKAGE(Qt6Widgets REQUIRED)
FIND_PACKAGE(Qt6OpenGL REQUIRED)
ADD_DEFINITIONS(-DUNICODE)
ADD_DEFINITIONS(-D_UNICODE)
ADD_DEFINITIONS(-DIFCQUERY_STATIC_LIB)
Expand All @@ -34,7 +34,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)

SET(viewer_dir ${IFCPP_SOURCE_DIR}/examples/SimpleViewerExampleQt)
SET(RESOURCES ${viewer_dir}/Resources/ifcplusplus.qrc)
QT5_ADD_RESOURCES(SimpleViewerExampleQt_RESOURCES_RCC ${RESOURCES})
QT6_ADD_RESOURCES(SimpleViewerExampleQt_RESOURCES_RCC ${RESOURCES})

FIND_PACKAGE(OpenSceneGraph REQUIRED osgDB osgUtil osgGA osgFX osgSim osgText osgViewer)

Expand Down Expand Up @@ -67,7 +67,7 @@ SET(IFCPPVIEWER_MOC_FILES
src/viewer/ViewerWidget.h
)

QT5_WRAP_CPP( IFCPPVIEWER_MOC_GENERATED_FILES ${IFCPPVIEWER_MOC_FILES})
QT6_WRAP_CPP( IFCPPVIEWER_MOC_GENERATED_FILES ${IFCPPVIEWER_MOC_FILES})

LINK_DIRECTORIES(${CMAKE_BINARY_DIR}/IfcPlusPlus/Debug)
LINK_DIRECTORIES(${CMAKE_BINARY_DIR}/IfcPlusPlus/${CMAKE_BUILD_TYPE})
Expand All @@ -84,10 +84,10 @@ set_target_properties(SimpleViewerExampleQt PROPERTIES VS_DEBUGGER_ENVIRONMENT "

target_link_libraries(SimpleViewerExampleQt
debug IfcPlusPlusd optimized IfcPlusPlus
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::OpenGL)
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::OpenGL)

If(UNIX)
# ON Debian Stretch with cmake 3.7.2 OPENTHREADS_LIBRARY_DEBUG is a not known variable
Expand Down Expand Up @@ -142,7 +142,7 @@ TARGET_INCLUDE_DIRECTORIES(SimpleViewerExampleQt
${OSGUTIL_INCLUDE_DIR}
${OSGANIMATION_INCLUDE_DIR}
${OSGTEXT_INCLUDE_DIR}
${Qt5Core_INCLUDE_DIRS}
${Qt6Core_INCLUDE_DIRS}
)

INSTALL(
Expand Down
2 changes: 1 addition & 1 deletion examples/SimpleViewerExampleQt/Resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ QFrame {
}

QWidget#OpenFileWidget { background-color: rgba(240,240,240,0.5); border: 1px solid #cecece; margin: 10px;}
QWidget#SettingsWidget { background-color: rgba(240,240,240,0.5); border: 1px solid #cecece; margin: 10px;}
QWidget#SettingsWidget { background-color: rgba(240,240,240,0.5); border: 1px solid #cecece;}

QStatusBar { height:20px;max-height: 24px;}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
<LocalDebuggerEnvironment>PATH=$(QTDIR)\bin%3b"$(QTDIR)\bin%3b$(PATH)</LocalDebuggerEnvironment>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<QtLastBackgroundBuild>2024-01-18T07:50:24.9045647Z</QtLastBackgroundBuild>
<QtLastBackgroundBuild>2024-01-26T15:10:49.9598175Z</QtLastBackgroundBuild>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<QtLastBackgroundBuild>2024-01-18T07:50:24.9860178Z</QtLastBackgroundBuild>
<QtLastBackgroundBuild>2024-01-26T15:10:50.0702063Z</QtLastBackgroundBuild>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<QtLastBackgroundBuild>2024-01-18T07:50:25.2048710Z</QtLastBackgroundBuild>
<QtLastBackgroundBuild>2024-01-26T15:10:50.1968036Z</QtLastBackgroundBuild>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<QtLastBackgroundBuild>2024-01-18T07:50:25.3122549Z</QtLastBackgroundBuild>
<QtLastBackgroundBuild>2024-01-26T15:10:50.2919150Z</QtLastBackgroundBuild>
</PropertyGroup>
</Project>
Loading

0 comments on commit 118636e

Please sign in to comment.