/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// // ===================== AI-CODE-FILE ====================== // Source: Gemini Pro 2.5 // Date: 2025-09-12 // Reviewer: alexander.tsymbal@opendesign.com // Issue: IFC-1858 // Notes: Originally manually created, modified with AI and manually 2025-09-12 // ========================================================= #include "OdaCommon.h" #include "ExIfcTutorial_ScheduleCreation.h" #include "IfcSimpleTypes.h" #include "IfcExamplesCommon.h" #include "IfcCore.h" #if defined(__APPLE__) || defined(__linux__) #include // DBL_MAX #endif #include "IfcCompound.h" #include "Ge/GeTrMeshSimplification.h" #include "Gi/GiGeometrySimplifier.h" #include "Gi/GiShellToolkit.h" #include "OdVector.h" ODRX_CONS_DEFINE_MEMBERS(Tutorial_ScheduleCreation_GeomWorldDraw, OdGiWorldDraw, RXIMPL_CONSTR); ODRX_CONS_DEFINE_MEMBERS(Tutorial_ScheduleCreation_WorldGeometry, OdGiWorldGeometry, RXIMPL_CONSTR); ODRX_CONS_DEFINE_MEMBERS(Tutorial_ScheduleCreation_EntityTraits, OdGiSubEntityTraits, RXIMPL_CONSTR); namespace { OdDAIObjectId createIfcTaskTime(OdDAI::ModelPtr pModel, int day, int month, int year) { // IFC TASK TIME CREATION OdIfc::OdIfcInstancePtr pTaskTime = pModel->createEntityInstance("ifctasktime"); OdDAI::Enum* dataOrigin, * durationType; pTaskTime->getAttr("dataorigin") >> dataOrigin; dataOrigin->setIntValue(1); // PREDICTED pTaskTime->getAttr("durationtype") >> durationType; durationType->setIntValue(1); // WORKTIME OdTimeStamp scheduleStartTime(OdTimeStamp::kInitLocalTime), scheduleFinishTime(OdTimeStamp::kInitLocalTime); scheduleStartTime.setDate(month, day, year); scheduleStartTime.setTime(8, 0, 0, 0); scheduleFinishTime.setDate(month, day + 1, year); scheduleFinishTime.setTime(8, 0, 0, 0); OdString scheduleStart, scheduleFinish; OdDAI::Utils::sdaiStrFTime(scheduleStartTime, L"%Y-%m-%dT%H:%M:%S", scheduleStart); OdDAI::Utils::sdaiStrFTime(scheduleFinishTime, L"%Y-%m-%dT%H:%M:%S", scheduleFinish); pTaskTime->putAttr("schedulestart", OdAnsiString(scheduleStart)); pTaskTime->putAttr("schedulefinish", OdAnsiString(scheduleFinish)); pTaskTime->putAttr("iscritical", OdDAI::Boolean::True); return pModel->appendEntityInstance(pTaskTime); } OdDAIObjectId createIfcTask(OdDAI::ModelPtr pModel, OdDAIObjectId ownerHistoryId, OdDAIObjectId productId, OdDAIObjectId taskTimeId) { OdIfc::OdIfcInstancePtr pTask = pModel->createEntityInstance("ifctask"); OdIfc::Utils::assignGlobalId(pTask); pTask->putAttr("ownerhistory", ownerHistoryId); pTask->putAttr("ismilestone", OdDAI::Boolean::False); OdIfc::OdIfcInstancePtr pProduct = productId.openObject(); OdAnsiString productName; pProduct->getAttr("name") >> productName; pTask->putAttr("name", productName); pTask->putAttr("tasktime", taskTimeId); return pModel->appendEntityInstance(pTask); } } Tutorial_ScheduleCreation::Tutorial_ScheduleCreation(const OdString& applicationName) : BaseIfcTutorial(applicationName) { m_tutorialArgsParser. add_param( std::make_shared>(m_ifcInputFileName, "inputFile", "full path to the input .ifc file.", false)); m_tutorialArgsParser. add_param( std::make_shared>(m_ifcOutputFileName, "outputFile", "full path to the output .ifc file.", false)); m_noWait = false; m_tutorialArgsParser .add_param( std::make_shared(m_noWait, "-NoWait", "disable \"press any key\" on finish.")); } Tutorial_ScheduleCreation::~Tutorial_ScheduleCreation() { } int Tutorial_ScheduleCreation::run(const MyServices& svcs, const std::vector& argv, std::ostream& resultStream) { auto parseResult = BaseIfcTutorial::run(svcs, argv, resultStream); if (parseResult != 0) { return parseResult; } // Return value for main int nRes = 0; Tutorial_ScheduleCreation_GeomWorldDraw::rxInit(); Tutorial_ScheduleCreation_WorldGeometry::rxInit(); Tutorial_ScheduleCreation_EntityTraits::rxInit(); try { /********************************************************************/ /* Proceed with IFC File and Model Loading */ /********************************************************************/ OdIfcFilePtr pIfcFile = svcs.createDatabase(); OdResult res = pIfcFile->readFile(m_ifcInputFileName); if (res == eOk) { odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: reading file %s is successful.\n"), OdString(m_ifcInputFileName).c_str()); } else { odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: reading file %s is failed.\n"), OdString(m_ifcInputFileName).c_str()); nRes = -1; } OdIfcModelPtr pModel = pIfcFile->getModel(sdaiRW); OdAnsiString schema = pModel->underlyingSchemaName(); if (schema.iCompare("IFC4") < 0) { odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: file schema should be IFC4 or newer.\n"), OdString(m_ifcInputFileName).c_str()); nRes = -1; } if (nRes == 0) { pIfcFile->setContextSelection(OdIfc::Utils::getDefaultRepresentationContextsSelection(pIfcFile, false)); pIfcFile->composeEntities(); /********************************************************************/ /* Gather all IfcProduct entities from the model */ /********************************************************************/ OdDAI::InstanceIteratorPtr pInstanceIterator = pModel->newIterator(); OdDAIObjectIds productsIds; OdDAIObjectId ownerHistoryId; // You need this for creating new entities while (!pInstanceIterator->done()) { OdDAIObjectId id = pInstanceIterator->id(); if (id.isValid()) { OdIfc::OdIfcInstancePtr pInst = id.openObject(); if (pInst->isKindOf("ifcproduct")) productsIds.append(id); else if (pInst->isKindOf("ifcownerhistory")) ownerHistoryId = id; } pInstanceIterator->step(); } /********************************************************************/ /* Create Instance of Your Custom Drawer and Vectorize Products */ /********************************************************************/ Tutorial_ScheduleCreation_GeomWorldDrawPtr pWorldDraw = Tutorial_ScheduleCreation_GeomWorldDraw::createObject(pModel); odPrintConsoleString(OD_T("\nStarting vectorization of products...\n")); // Iterate through the products and draw them. for (const OdDAIObjectId& productId : productsIds) { OdIfc::OdIfcInstancePtr pProduct = productId.openObject(); if (!pProduct.isNull()) { // Set the current product ID in the context so the shell() method knows which product it's processing. InstanceContext ctx; ctx.m_idIfcProduct = productId; pWorldDraw->setContext(ctx); // This call vectorizes the product's geometry. pProduct->subWorldDraw(pWorldDraw); } } // Clear the productsIds array as it is no longer needed. productsIds.clear(); odPrintConsoleString(OD_T("\nVectorization complete.\n")); // Retrieve the geometry object and get the results. // After all products have been drawn, the multimap is fully populated. Tutorial_ScheduleCreation_WorldGeometry* pGeometry = static_cast(&pWorldDraw->geometry()); const std::multimap& zOrderedProductMap = pGeometry->getZOrderedProducts(); odPrintConsoleString(OD_T("\nFound %d IfcProduct entities, that are able to be connected with IfcTasks.\n"), zOrderedProductMap.size()); // The next section creates a simple construction schedule based on the products // that were sorted by their Z-coordinate in the previous steps. // The logic assumes that objects on a similar Z-level belong to the same floor // and that construction proceeds from the lowest floor to the highest. // Tutorial Step 1: Initialize schedule variables. // You'll use these to keep track of dates and the state of our schedule creation. OdTimeStamp startDate(OdTimeStamp::kInitLocalTime); int month = startDate.month(), day = startDate.day(), year = startDate.year(), previousFinishMonth = month, previousFinishDay = day, previousFinishYear = year; bool isFirstOnLevel = true; // A flag to track if you're processing the first product on a new floor. OdIfc::OdIfcInstancePtr pLevelTask, pLevelTaskTime; // Pointers to the main task for the current floor. OdDAIObjectIds subTasksIds; // A list to hold all sub-tasks for a single floor. double previousLevel = zOrderedProductMap.begin()->first; // The Z-coordinate of the previously processed product. unsigned int levelCounter = 1; // A counter to name our floor-level tasks (e.g., "1 Level"). odPrintConsoleString(OD_T("\n--- Creating Schedule from Z-Sorted Products ---\n")); // Tutorial Step 2: Iterate through the Z-sorted map of products. // Because you used a multimap, this loop processes products from the ground up. for (const auto& product : zOrderedProductMap) { /* * IFC TASK CREATION * * For each floor there is one main task, * that contains information about time of whole floor creation. * This task has links to all floor subtasks and has no assigned product * */ // Tutorial Step 3: Check if you have moved to a new floor/level. // You compare the current product's Z-level with the previous one. If it's higher, // you assume it's a new level. This is the moment to finalize the previous level's task. if (OdLess(previousLevel, product.first)) { isFirstOnLevel = true; if (!pLevelTask.isNull()) { odPrintConsoleString(OD_T("\nFinalizing level task (%d)..."), levelCounter); // Finalize the finish date for the previous level's main task. OdTimeStamp scheduleFinishTime(OdTimeStamp::kInitLocalTime); scheduleFinishTime.setDate(previousFinishMonth, previousFinishDay, previousFinishYear); scheduleFinishTime.setTime(8, 0, 0, 0); OdString scheduleFinish; OdDAI::Utils::sdaiStrFTime(scheduleFinishTime, L"%Y-%m-%dT%H:%M:%S", scheduleFinish); pLevelTaskTime->putAttr("schedulefinish", OdAnsiString(scheduleFinish)); // Give the main level task a descriptive name. OdAnsiString name = OdAnsiString(std::to_string(levelCounter++).c_str()); name += " Level"; pLevelTask->putAttr("name", name); // Create an IfcRelNests relationship. This is how you create a hierarchy, // linking the main floor task as the parent to all the individual product tasks (sub-tasks). OdIfc::OdIfcInstancePtr pRelNests = pModel->createEntityInstance("ifcrelnests"); OdIfc::Utils::assignGlobalId(pRelNests); pRelNests->putAttr("relatingobject", OdDAIObjectId(pLevelTask->id())); pRelNests->putAttr("relatedobjects", subTasksIds); subTasksIds.clear(); // Clear the list to start fresh for the next level. pModel->appendEntityInstance(pRelNests); } } // Tutorial Step 4: Create the main task for a new level. // If this is the first product on a new level, you need a new parent task to represent the whole floor. if (isFirstOnLevel) { odPrintConsoleString(OD_T("\nCreating new level task for Z = %f"), product.first); OdDAIObjectId levelTaskTimeId = createIfcTaskTime(pModel, day, month, year); pLevelTaskTime = levelTaskTimeId.openObject(); OdDAIObjectId levelTaskId = createIfcTask(pModel, ownerHistoryId, product.second, levelTaskTimeId); // Note: No product is assigned to the level task itself. pLevelTask = levelTaskId.openObject(); isFirstOnLevel = false; // Unset the flag for this level. } // Tutorial Step 5: Create a specific task for the individual product. // Every product gets its own IfcTask and IfcTaskTime. odPrintConsoleString(OD_T("\n - Creating task for product #%llu"), product.second.getHandle()); OdDAIObjectId taskTimeId = createIfcTaskTime(pModel, day, month, year); OdDAIObjectId taskId = createIfcTask(pModel, ownerHistoryId, product.second, taskTimeId); // Add this new task to our list of sub-tasks for the current level. subTasksIds.append(taskId); // Tutorial Step 6: Link the product to its task. // An IfcRelAssignsToProduct is created to formally associate the IfcProduct with its IfcTask. // Note: This relationship is between a product and its specific task, not the main level task. OdIfc::OdIfcInstancePtr pRelAssignsToProduct = pModel->createEntityInstance("ifcrelassignstoproduct"); OdIfc::Utils::assignGlobalId(pRelAssignsToProduct); pRelAssignsToProduct->putAttr("ownerhistory", ownerHistoryId); OdDAIObjectIds relatedObjectsIds; relatedObjectsIds.append(taskId); pRelAssignsToProduct->putAttr("relatedobjects", relatedObjectsIds); OdDAI::Select* productSelect; pRelAssignsToProduct->getAttr("relatingproduct") >> productSelect; pModel->appendEntityInstance(pRelAssignsToProduct); productSelect->setHandle(product.second); // Tutorial Step 7: Advance the schedule time. // For this simple example, you assume each product takes one day to install. // you increment the date and handle month/year rollovers. previousFinishDay = day + 1; previousFinishMonth = month; previousFinishYear = year; ++day; if (day > 28) // Using 28 for simplicity to avoid complex calendar logic { day = 1; ++month; if (month > 12) { ++year; month = 1; } } // Update the previousLevel to the current Z-coordinate for the next iteration. previousLevel = product.first; } // Tutorial Step 8: Finalize the very last level's task. // The loop finalizes a level's main task only when it detects the *next* level. // Therefore, the task for the highest level in the building is never finalized inside the loop. // You must do it here, after the loop has completed. if (!pLevelTask.isNull()) { odPrintConsoleString(OD_T("\nFinalizing level task (%d)..."), levelCounter); // Finalize the finish date for the previous level's main task. OdTimeStamp scheduleFinishTime(OdTimeStamp::kInitLocalTime); scheduleFinishTime.setDate(previousFinishMonth, previousFinishDay, previousFinishYear); scheduleFinishTime.setTime(8, 0, 0, 0); OdString scheduleFinish; OdDAI::Utils::sdaiStrFTime(scheduleFinishTime, L"%Y-%m-%dT%H:%M:%S", scheduleFinish); pLevelTaskTime->putAttr("schedulefinish", OdAnsiString(scheduleFinish)); // Give the main level task a descriptive name. OdAnsiString name = OdAnsiString(std::to_string(levelCounter++).c_str()); name += " Level"; pLevelTask->putAttr("name", name); // Create an IfcRelNests relationship. This is how you create a hierarchy, // linking the main floor task as the parent to all the individual product tasks (sub-tasks). OdIfc::OdIfcInstancePtr pRelNests = pModel->createEntityInstance("ifcrelnests"); OdIfc::Utils::assignGlobalId(pRelNests); pRelNests->putAttr("relatingobject", OdDAIObjectId(pLevelTask->id())); pRelNests->putAttr("relatedobjects", subTasksIds); subTasksIds.clear(); // Clear the list to start fresh for the next level. pModel->appendEntityInstance(pRelNests); } odPrintConsoleString(OD_T("\nSchedule creation loop finished.")); odPrintConsoleString(OD_T("\n---------------------------------------\n")); pIfcFile->unresolveEntities(); OdResult finishResult = pIfcFile->writeFile(m_ifcOutputFileName); if(finishResult == eOk) odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: File with work schedule was created successfully.\n")); else odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: Could not write the output IFC file.\n")); } } catch (OdError& e) { odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: Error: %ls"), e.description().c_str()); nRes = -1; } catch (...) { odPrintConsoleString(OD_T("\nTutorial_ScheduleCreation: Unexpected error.")); nRes = -1; } Tutorial_ScheduleCreation_EntityTraits::rxUninit(); Tutorial_ScheduleCreation_WorldGeometry::rxUninit(); Tutorial_ScheduleCreation_GeomWorldDraw::rxUninit(); return nRes; } void Tutorial_ScheduleCreation_WorldGeometry::pushModelTransform(const OdGeVector3d& normal) { } void Tutorial_ScheduleCreation_WorldGeometry::pushModelTransform(const OdGeMatrix3d& xfm) { tutorial_ScheduleCreationWorldDraw()->context().m_transf.push(xfm); } void Tutorial_ScheduleCreation_WorldGeometry::popModelTransform() { tutorial_ScheduleCreationWorldDraw()->context().m_transf.pop(); } void Tutorial_ScheduleCreation_WorldGeometry::shell( OdInt32 numVertices, const OdGePoint3d* vertexList, OdInt32 faceListSize, const OdInt32* faceList, const OdGiEdgeData* pEdgeData/* = 0*/, const OdGiFaceData* pFaceData/* = 0*/, const OdGiVertexData* pVertexData/* = 0*/) { if (numVertices > 0) { OdDAIObjectId productId = tutorial_ScheduleCreationWorldDraw()->context().m_idIfcProduct; if (productId.isNull()) return; if (!m_processedProducts.insert(productId).second) { // The ID was already in the set, so you have already processed this product and now need to skip it. return; } OdGeExtents3d extents; const OdGeMatrix3d& transform = tutorial_ScheduleCreationWorldDraw()->context().m_transf.transformation(); for (OdInt32 i = 0; i < numVertices; ++i) { OdGePoint3d transformedVertex = vertexList[i]; transformedVertex.transformBy(transform); extents.addPoint(transformedVertex); } if (extents.isValidExtents()) { double zCoordinate = (extents.minPoint().z + extents.maxPoint().z) / 2.0; m_zOrderedProducts.insert(std::make_pair(zCoordinate, productId)); } } } void Tutorial_ScheduleCreation_WorldGeometry::polyline( OdInt32 numVertices, const OdGePoint3d* vertexList, const OdGeVector3d* pNormal /*= 0*/, OdGsMarker baseSubEntMarker /*= -1*/) { } void Tutorial_ScheduleCreation_WorldGeometry::draw(const OdGiDrawable* pDrawable) { if (OdIfc::OdIfcCompoundPtr compound = OdIfc::OdIfcCompound::cast(pDrawable)) { compound->subWorldDraw(m_pWd); OdDAI::ApplicationInstancePtr inst = OdIfc::OdIfcCompound::earlyAccess(compound); } else if (OdDAI::ApplicationInstancePtr inst = pDrawable) inst->subWorldDraw(m_pWd); }