diff --git a/src/Mod/Draft/DxfImportDialog.py b/src/Mod/Draft/DxfImportDialog.py index a1c7fec800..c260b6fd92 100644 --- a/src/Mod/Draft/DxfImportDialog.py +++ b/src/Mod/Draft/DxfImportDialog.py @@ -76,8 +76,8 @@ class DxfImportDialog: self.dialog.radio_ImportAs_Shapes.setEnabled(True) self.dialog.radio_ImportAs_Fused.setEnabled(True) else: - self.dialog.radio_ImportAs_Draft.setEnabled(False) - self.dialog.radio_ImportAs_Primitives.setEnabled(False) + self.dialog.radio_ImportAs_Draft.setEnabled(True) + self.dialog.radio_ImportAs_Primitives.setEnabled(True) self.dialog.radio_ImportAs_Shapes.setEnabled(True) self.dialog.radio_ImportAs_Fused.setEnabled(True) diff --git a/src/Mod/Draft/Resources/ui/preferences-dxf.ui b/src/Mod/Draft/Resources/ui/preferences-dxf.ui index c4a78612cd..881e5f5e7c 100644 --- a/src/Mod/Draft/Resources/ui/preferences-dxf.ui +++ b/src/Mod/Draft/Resources/ui/preferences-dxf.ui @@ -111,19 +111,19 @@ the 'dxf_library' addon from the Addon Manager. - false + true Creates fully parametric Draft objects. Block definitions are imported as reusable objects (Part Compounds) and instances become `App::Link` objects, maintaining the block structure. Best for full integration with the Draft -Workbench. (Legacy importer only) +Workbench. Editable draft objects (Highest fidelity, slowest) - DxfImportMode + dxfImportAsDraft Mod/Draft @@ -139,19 +139,19 @@ Workbench. (Legacy importer only) - false + true Creates parametric Part objects (e.g., Part::Line, Part::Circle). Block definitions are imported as reusable objects (Part Compounds) and instances become `App::Link` objects, maintaining the block structure. Best for -script-based post-processing. (Not yet implemented) +script-based post-processing and Part Workbench integration. Editable part primitives (High fidelity, slower) - DxfImportMode + dxfImportAsPrimitives Mod/Draft @@ -178,7 +178,7 @@ objects, maintaining the block structure. Good for referencing and measuring.true - DxfImportMode + dxfImportAsShapes Mod/Draft @@ -196,13 +196,13 @@ objects, maintaining the block structure. Good for referencing and measuring. Merges all geometry per layer into a single, non-editable shape. Block structures are not preserved; their geometry becomes part of the layer's -shape. Best for viewing very large files with maximum performance. +shape. Best for importing and viewing very large files with maximum performance. Fused part shapes (Lowest fidelity, fastest) - DxfImportMode + dxfImportAsFused Mod/Draft diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index cc67c5292b..4e169752a7 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -2790,12 +2790,117 @@ def warn(dxfobject, num=None): badobjects.append(dxfobject) +def _import_dxf_file(filename, doc_name=None): + """ + Internal helper to handle the core logic for both open and insert. + """ + hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False) + readPreferences() + + # --- Dialog Workflow --- + try: + if gui: + FreeCADGui.suspendWaitCursor() + + if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True): + try: + import ImportGui + entity_counts = ImportGui.preScanDxf(filename) + except Exception: + entity_counts = {} + + from DxfImportDialog import DxfImportDialog + dlg = DxfImportDialog(entity_counts) + + if dlg.exec_(): + # Save the integer mode from the pop-up dialog. + hGrp.SetInt("DxfImportMode", dlg.get_selected_mode()) + + # Keep the main preferences booleans + # in sync with the choice just made in the pop-up dialog. + mode = dlg.get_selected_mode() + params.set_param("dxfImportAsDraft", mode == 0) + params.set_param("dxfImportAsPrimitives", mode == 1) + params.set_param("dxfImportAsShapes", mode == 2) + params.set_param("dxfImportAsFused", mode == 3) + hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again()) + else: + return None, None, None, None # Return None to indicate cancellation + finally: + if gui: + FreeCADGui.resumeWaitCursor() + + import_mode = hGrp.GetInt("DxfImportMode", 2) + + # --- Document Handling --- + if doc_name: # INSERT operation + try: + doc = FreeCAD.getDocument(doc_name) + except NameError: + doc = FreeCAD.newDocument(doc_name) + FreeCAD.setActiveDocument(doc_name) + else: # OPEN operation + docname = os.path.splitext(os.path.basename(filename))[0] + doc = FreeCAD.newDocument(docname) + doc.Label = docname + FreeCAD.setActiveDocument(doc.Name) + + # --- Core Import Execution --- + processing_start_time = time.perf_counter() + + if is_draft_mode: + # For Draft mode, we tell the C++ importer to create Part Primitives first. + hGrp.SetInt("DxfImportMode", 1) + + # Take snapshot of objects before import + objects_before = set(doc.Objects) + + stats = None # For C++ importer stats + if use_legacy: + getDXFlibs() + if dxfReader: + processdxf(doc, filename) + else: + errorDXFLib(gui) + return None, None + else: # Modern C++ Importer + if gui: + import ImportGui + stats = ImportGui.readDXF(filename) + else: + import Import + stats = Import.readDXF(filename) + + # Find the newly created objects + objects_after = set(doc.Objects) + newly_created_objects = objects_after - objects_before + + # Restore the original mode setting if we changed it + if is_draft_mode: + hGrp.SetInt("DxfImportMode", 0) + + # --- Post-processing step --- + if is_draft_mode and newly_created_objects: + post_process_to_draft(doc, newly_created_objects) + + Draft.convert_draft_texts() # This is a general utility that should run for both importers + doc.recompute() + + processing_end_time = time.perf_counter() + + # Return the results for the reporter + return doc, stats, processing_start_time, processing_end_time + +# --- REFACTORED open() and insert() functions --- + def open(filename): """Open a file and return a new document. - If the global variable `dxfUseLegacyImporter` exists, - it will process `filename` with `processdxf`. - Otherwise, it will use the `Import` module, `Import.readDXF(filename)`. + This function handles the import of a DXF file into a new document. + It shows an import dialog for the modern C++ importer if configured to do so. + It manages the import workflow, including pre-processing, calling the + correct backend (legacy or modern C++), and post-processing. Parameters ---------- @@ -2804,175 +2909,38 @@ def open(filename): Returns ------- - App::Document - The new document object with objects and shapes built from `filename`. - - To do - ----- - Use local variables, not global variables. + App::Document or None + The new document object with imported content, or None if the + operation was cancelled or failed. """ - hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") - use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False) + doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=None) - # The C++ layer (`Gui::Application::importFrom`) has set the WaitCursor. - # We must temporarily suspend it to show our interactive dialog. - try: - if gui: - FreeCADGui.suspendWaitCursor() - - # --- Dialog Workflow --- - if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True): - try: - import ImportGui - # This C++ function will need to be created in a later step - entity_counts = ImportGui.preScanDxf(filename) - except Exception: - entity_counts = {} - - from DxfImportDialog import DxfImportDialog - dlg = DxfImportDialog(entity_counts) - - if dlg.exec_(): - # User clicked OK, save settings from the dialog - hGrp.SetInt("DxfImportMode", dlg.get_selected_mode()) - hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again()) - else: - # User clicked Cancel, abort the entire operation - FCC.PrintLog("DXF import cancelled by user.\n") - return - else: - # If we don't show the dialog, we still need to read preferences - # to ensure the correct backend logic is triggered. - readPreferences() - - finally: - # --- CRITICAL: Always resume the wait state before returning to C++ --- - # This restores the wait cursor and event filter so the subsequent - # blocking C++ call behaves as expected within the C++ scope. - if gui: - FreeCADGui.resumeWaitCursor() - - # --- Proceed with the blocking import logic --- - total_start_time = time.perf_counter() - - if use_legacy: - getDXFlibs() - if dxfReader: - docname = os.path.splitext(os.path.basename(filename))[0] - doc = FreeCAD.newDocument(docname) - doc.Label = docname - processdxf(doc, filename) - return doc - else: - errorDXFLib(gui) - return None - else: # Modern C++ Importer - docname = os.path.splitext(os.path.basename(filename))[0] - doc = FreeCAD.newDocument(docname) - doc.Label = docname - FreeCAD.setActiveDocument(doc.Name) - stats = None - - if gui: - import ImportGui - stats = ImportGui.readDXF(filename) - else: - import Import - stats = Import.readDXF(filename) - - Draft.convert_draft_texts() - doc.recompute() - - total_end_time = time.perf_counter() - if stats: - # Report PROCESSING time only, not user dialog time. - reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time) - reporter.report_to_console() - - return doc + if doc and stats: + reporter = DxfImportReporter(filename, stats, end_time - start_time) + reporter.report_to_console() + return doc def insert(filename, docname): """Import a file into the specified document. + This function handles the import of a DXF file into a specified document. + If the document does not exist, it will be created. It shows an import + dialog for the modern C++ importer if configured to do so. + Parameters ---------- filename : str The path to the file to import. - docname : str - The name of an `App::Document` instance into which - the objects and shapes from `filename` will be imported. - - If the document doesn't exist, it is created - and set as the active document. - - To do - ----- - Use local variables, not global variables. + The name of an App::Document instance to import the content into. """ - hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") - use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False) + doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=docname) - try: - if gui: - FreeCADGui.suspendWaitCursor() + if doc and stats: + reporter = DxfImportReporter(filename, stats, end_time - start_time) + reporter.report_to_console() - # --- Dialog Workflow --- - if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True): - try: - import ImportGui - entity_counts = ImportGui.preScanDxf(filename) - except Exception: - entity_counts = {} - - from DxfImportDialog import DxfImportDialog - dlg = DxfImportDialog(entity_counts) - - if dlg.exec_(): - hGrp.SetInt("DxfImportMode", dlg.get_selected_mode()) - hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again()) - else: - FCC.PrintLog("DXF insert cancelled by user.\n") - return - else: - readPreferences() - - finally: - if gui: - FreeCADGui.resumeWaitCursor() - - # --- Proceed with the blocking insert logic --- - total_start_time = time.perf_counter() - - try: - doc = FreeCAD.getDocument(docname) - except NameError: - doc = FreeCAD.newDocument(docname) - FreeCAD.setActiveDocument(docname) - - if use_legacy: - getDXFlibs() - if dxfReader: - processdxf(doc, filename) - else: - errorDXFLib(gui) - else: # Modern C++ Importer - stats = None - if gui: - import ImportGui - stats = ImportGui.readDXF(filename) - else: - import Import - stats = Import.readDXF(filename) - - Draft.convert_draft_texts() - doc.recompute() - - total_end_time = time.perf_counter() - if stats: - reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time) - reporter.report_to_console() def getShapes(filename): """Read a DXF file, and return a list of shapes from its contents. @@ -4274,32 +4242,46 @@ def readPreferences(): dxfUseLegacyImporter = hGrp.GetBool("dxfUseLegacyImporter", False) - # This logic is now only needed for the legacy importer. + # Synchronization Bridge (Booleans -> Integer) + # Read the boolean parameters from the main preferences dialog. Based on which one is true, set + # the single 'DxfImportMode' integer parameter that the C++ importer and legacy importer logic + # rely on. This ensures the setting from the main preferences is always respected at the start + # of an import. + if hGrp.GetBool("dxfImportAsDraft", False): + import_mode = 0 + elif hGrp.GetBool("dxfImportAsPrimitives", False): + import_mode = 1 + elif hGrp.GetBool("dxfImportAsFused", False): + import_mode = 3 + else: # Default to "Individual part shapes" + import_mode = 2 + hGrp.SetInt("DxfImportMode", import_mode) + + # The legacy importer logic now reads the unified import_mode integer. # The modern importer reads its settings directly in C++. if dxfUseLegacyImporter: # Legacy override for sketch creation takes highest priority dxfCreateSketch = hGrp.GetBool("dxfCreateSketch", False) - if dxfCreateSketch: + if dxfCreateSketch: # dxfCreateSketch overrides the import mode for the legacy importer dxfCreatePart = False dxfCreateDraft = False dxfMakeBlocks = False - else: - # Read the new unified mode parameter and translate it to the old flags - # 0=Draft, 1=Primitives, 2=Shapes, 3=Fused - import_mode = hGrp.GetInt("DxfImportMode", 2) # Default to "Individual shapes" - if import_mode == 3: # Fused part shapes - dxfMakeBlocks = True - dxfCreatePart = False - dxfCreateDraft = False - elif import_mode == 0: # Editable draft objects - dxfMakeBlocks = False - dxfCreatePart = False - dxfCreateDraft = True - else: # Individual part shapes or Primitives - dxfMakeBlocks = False - dxfCreatePart = True - dxfCreateDraft = False + # The 'import_mode' variable is now set by the UI synchronization bridge that runs just + # before this block. We now translate the existing 'import_mode' variable into the old + # flags. + elif import_mode == 0: # Editable draft objects + dxfMakeBlocks = False + dxfCreatePart = False + dxfCreateDraft = True + elif import_mode == 3: # Fused part shapes + dxfMakeBlocks = True + dxfCreatePart = False + dxfCreateDraft = False + else: # Individual part shapes or Primitives (modes 1 and 2) + dxfMakeBlocks = False + dxfCreatePart = True + dxfCreateDraft = False # The legacy importer still uses these global variables, so we read them all. dxfDiscretizeCurves = hGrp.GetBool("DiscretizeEllipses", True) @@ -4321,6 +4303,79 @@ def readPreferences(): dxfBrightBackground = isBrightBackground() dxfDefaultColor = getColor() + +def post_process_to_draft(doc, new_objects): + """ + Converts a list of newly created Part primitives and placeholders + into their corresponding Draft objects. + """ + if not new_objects: + return + + FCC.PrintMessage("Post-processing {} objects to Draft types...\n".format(len(new_objects))) + + objects_to_delete = [] + + for obj in list(new_objects): # Iterate over a copy + if App.isdeleted(obj): + continue + + if obj.isDerivedFrom("Part::Feature"): + # Handles Part::Vertex, Part::Line, Part::Circle, Part::Compound, + # and Part::Features containing Ellipses/Splines. + try: + Draft.upgrade([obj], delete=True) + except Exception as e: + FCC.PrintWarning("Could not upgrade {} to Draft object: {}\n".format(obj.Label, str(e))) + + elif obj.isDerivedFrom("App::FeaturePython") and hasattr(obj, "DxfEntityType"): + # This is one of our placeholders + entity_type = obj.DxfEntityType + + if entity_type == "DIMENSION": + try: + # 1. Create an empty Draft Dimension + dim = doc.addObject("App::FeaturePython", "Dimension") + Draft.Dimension(dim) + if gui: + from Draft import _ViewProviderDimension + _ViewProviderDimension(dim.ViewObject) + + # 2. Copy properties directly from the placeholder + dim.Start = obj.Start + dim.End = obj.End + dim.Dimline = obj.Dimline + dim.Placement = obj.Placement + + objects_to_delete.append(obj) + except Exception as e: + FCC.PrintWarning("Could not create Draft Dimension from {}: {}\n".format(obj.Label, str(e))) + + elif entity_type == "TEXT": + try: + # 1. Create a Draft Text object + text_obj = Draft.make_text(obj.Text) + + # 2. Copy properties + text_obj.Placement = obj.Placement + if gui: + # TEXTSCALING is a global defined at the top of importDXF.py + text_obj.ViewObject.FontSize = obj.DxfTextHeight * TEXTSCALING + + objects_to_delete.append(obj) + except Exception as e: + FCC.PrintWarning("Could not create Draft Text from {}: {}\n".format(obj.Label, str(e))) + + # Perform the deletion of placeholders after the loop + for obj in objects_to_delete: + try: + doc.removeObject(obj.Name) + except Exception: + pass + + doc.recompute() + + class DxfImportReporter: """Formats and reports statistics from a DXF import process.""" def __init__(self, filename, stats_dict, total_time=0.0): diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.cpp b/src/Mod/Import/App/dxf/ImpExpDxf.cpp index 3ad107dc58..5bb8c3f4cb 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.cpp +++ b/src/Mod/Import/App/dxf/ImpExpDxf.cpp @@ -28,28 +28,37 @@ #include #endif #include +#include #include #include #include +#include #include +#include +#include #include #include #include #include +#include +#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #endif @@ -66,10 +75,12 @@ #include #include #include -#include #include #include +#include +#include #include +#include #include #include "ImpExpDxf.h" @@ -81,6 +92,81 @@ using namespace Import; using BRepAdaptor_HCurve = BRepAdaptor_Curve; #endif +namespace +{ + +Part::Circle* createCirclePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name); +Part::Line* createLinePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name); + +} // namespace + +namespace +{ + +// Helper function to create and configure a Part::Circle primitive from a TopoDS_Edge +Part::Circle* createCirclePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name) +{ + auto* p = doc->addObject(name); + if (!p) { + return nullptr; + } + + TopLoc_Location loc; + Standard_Real first, last; + Handle(Geom_Curve) aCurve = BRep_Tool::Curve(edge, loc, first, last); + + if (aCurve->IsInstance(Geom_Circle::get_type_descriptor())) { + Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(aCurve); + p->Radius.setValue(circle->Radius()); + + // The axis contains the full transformation (location and orientation). + gp_Ax2 axis = circle->Position().Transformed(loc.Transformation()); + gp_Pnt center = axis.Location(); + gp_Dir xDir = axis.XDirection(); + gp_Dir yDir = axis.YDirection(); + gp_Dir zDir = axis.Direction(); + + Base::Placement plc; + plc.setPosition(Base::Vector3d(center.X(), center.Y(), center.Z())); + plc.setRotation( + Base::Rotation::makeRotationByAxes(Base::Vector3d(xDir.X(), xDir.Y(), xDir.Z()), + Base::Vector3d(yDir.X(), yDir.Y(), yDir.Z()), + Base::Vector3d(zDir.X(), zDir.Y(), zDir.Z()))); + p->Placement.setValue(plc); + + // Set angles for arcs + BRep_Tool::Range(edge, first, last); + p->Angle1.setValue(Base::toDegrees(first)); + p->Angle2.setValue(Base::toDegrees(last)); + } + return p; +} + +// Helper function to create and configure a Part::Line primitive from a TopoDS_Edge +Part::Line* createLinePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name) +{ + auto* p = doc->addObject(name); + if (!p) { + return nullptr; + } + + TopoDS_Vertex v1, v2; + TopExp::Vertices(edge, v1, v2); + gp_Pnt p1 = BRep_Tool::Pnt(v1); + gp_Pnt p2 = BRep_Tool::Pnt(v2); + + p->X1.setValue(p1.X()); + p->Y1.setValue(p1.Y()); + p->Z1.setValue(p1.Z()); + p->X2.setValue(p2.X()); + p->Y2.setValue(p2.Y()); + p->Z2.setValue(p2.Z()); + + return p; +} + +} // namespace + std::map ImpExpDxfRead::PreScan(const std::string& filepath) { std::map counts; @@ -136,17 +222,6 @@ void ImpExpDxfRead::StartImport() bool ImpExpDxfRead::ReadEntitiesSection() { - // TODO: remove this once the unsupported modes have been implemented. - // Perform a one-time check for unsupported modes - if (m_importMode == ImportMode::EditableDraft) { - UnsupportedFeature("Import as 'Editable draft objects' is not yet implemented."); - // We can continue, and the switch statements below will do nothing, - // resulting in an empty import for geometry, which is correct behavior. - } - else if (m_importMode == ImportMode::EditablePrimitives) { - UnsupportedFeature("Import as 'Editable part primitives' is not yet implemented."); - } - // After parsing the BLOCKS section, compose all block definitions // into FreeCAD objects before processing the ENTITIES section. ComposeBlocks(); @@ -284,8 +359,10 @@ void ImpExpDxfRead::ComposeFlattenedBlock(const std::string& blockName, std::list shapeCollection; // 4. Process primitive geometry. - for (const auto& [attributes, shapeList] : blockData.Shapes) { - shapeCollection.insert(shapeCollection.end(), shapeList.begin(), shapeList.end()); + for (const auto& [attributes, builderList] : blockData.GeometryBuilders) { + for (const auto& builder : builderList) { + shapeCollection.push_back(builder.shape); + } } // 5. Process nested inserts recursively. @@ -351,8 +428,7 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName, const Block& blockData = it->second; // 3. Create the master Part::Compound for this block definition. - std::string compName = "BLOCK_"; - compName += blockName; + std::string compName = "BLOCK_" + blockName; auto blockCompound = document->addObject( document->getUniqueObjectName(compName.c_str()).c_str()); m_blockDefinitionGroup->addObject(blockCompound); @@ -360,7 +436,7 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName, blockCompound->Visibility.setValue(false); this->m_blockDefinitions[blockName] = blockCompound; - std::vector linkedObjects; + std::vector childObjects; // 4. Recursively Compose and Link Nested Inserts. for (const auto& insertAttrPair : blockData.Inserts) { @@ -387,96 +463,103 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName, link->ScaleVector.setValue(nestedInsert.Scale); link->Visibility.setValue(false); IncrementCreatedObjectCount(); - linkedObjects.push_back(link); + childObjects.push_back(link); } } } - // 5. Create and Link Primitive Geometry. - // Iterate through each attribute group (e.g., each layer within the block). - for (const auto& [attributes, shapeList] : blockData.Shapes) { - // Then, iterate through each shape in that group and create a separate feature for it. - for (const auto& shape : shapeList) { - if (!shape.IsNull()) { - std::string cleanBlockLabel = blockName; - if (!cleanBlockLabel.empty() && std::isdigit(cleanBlockLabel[0])) { - // Workaround for FreeCAD's unique name generator, which prepends an underscore - // to names that start with a digit. We add our own prefix. - cleanBlockLabel.insert(0, "_"); + // 5. Create and Link Primitive Geometry from the collected builders. + for (const auto& [attributes, builderList] : blockData.GeometryBuilders) { + this->m_entityAttributes = attributes; + + for (const auto& builder : builderList) { + App::DocumentObject* newObject = nullptr; + switch (builder.type) { + case GeometryBuilder::PrimitiveType::None: { + auto* p = document->addObject("Shape"); + p->Shape.setValue(builder.shape); + newObject = p; + break; } - else if (!cleanBlockLabel.empty() && std::isdigit(cleanBlockLabel.back())) { - // Add a trailing underscore to prevent the unique name generator - // from incrementing the number in the block's name. - cleanBlockLabel += "_"; + case GeometryBuilder::PrimitiveType::Point: { + auto* p = document->addObject("Point"); + TopoDS_Vertex v = TopoDS::Vertex(builder.shape); + gp_Pnt pnt = BRep_Tool::Pnt(v); + p->Placement.setValue(Base::Placement(Base::Vector3d(pnt.X(), pnt.Y(), pnt.Z()), + Base::Rotation())); + newObject = p; + break; } - // Determine a more descriptive name for the primitive feature. - std::string type_suffix = "Shape"; - if (shape.ShapeType() == TopAbs_EDGE) { - BRepAdaptor_Curve adaptor(TopoDS::Edge(shape)); - switch (adaptor.GetType()) { - case GeomAbs_Line: - type_suffix = "Line"; - break; - case GeomAbs_Circle: - type_suffix = "Circle"; - break; - case GeomAbs_Ellipse: - type_suffix = "Ellipse"; - break; - case GeomAbs_BSplineCurve: - type_suffix = "BSpline"; - break; - case GeomAbs_BezierCurve: - type_suffix = "Bezier"; - break; - default: - type_suffix = "Edge"; - break; + case GeometryBuilder::PrimitiveType::Line: { + newObject = createLinePrimitive(TopoDS::Edge(builder.shape), document, "Line"); + break; + } + case GeometryBuilder::PrimitiveType::Circle: + case GeometryBuilder::PrimitiveType::Arc: { + const char* name = + (builder.type == GeometryBuilder::PrimitiveType::Circle) ? "Circle" : "Arc"; + auto* p = createCirclePrimitive(TopoDS::Edge(builder.shape), document, name); + if (!p) { + break; // Helper function failed } - } - else if (shape.ShapeType() == TopAbs_VERTEX) { - type_suffix = "Vertex"; - } - else if (shape.ShapeType() == TopAbs_WIRE) { - type_suffix = "Wire"; - } - else if (shape.ShapeType() == TopAbs_FACE) { - type_suffix = "Face"; - } - else if (shape.ShapeType() == TopAbs_SHELL) { - type_suffix = "Shell"; - } - else if (shape.ShapeType() == TopAbs_SOLID) { - type_suffix = "Solid"; - } - else if (shape.ShapeType() == TopAbs_COMPOUND) { - type_suffix = "Compound"; - } - std::string primitive_base_label = cleanBlockLabel + "_" + type_suffix; - // Use getStandardObjectLabel to get a unique user-facing label (e.g., - // "block01_Line001") while keeping the internal object name clean. - auto geomFeature = document->addObject( - document->getStandardObjectLabel(primitive_base_label.c_str(), 3).c_str()); + // For full circles, ensure the angles span the full 360 degrees + if (builder.type == GeometryBuilder::PrimitiveType::Circle) { + p->Angle1.setValue(0.0); + p->Angle2.setValue(360.0); + } + newObject = p; + break; + } + case GeometryBuilder::PrimitiveType::PolylineCompound: { + auto* p = document->addObject("Polyline"); + std::vector segments; + TopExp_Explorer explorer(builder.shape, TopAbs_EDGE); + for (; explorer.More(); explorer.Next()) { + TopoDS_Edge edge = TopoDS::Edge(explorer.Current()); + BRepAdaptor_Curve adaptor(edge); + App::DocumentObject* segment = nullptr; + if (adaptor.GetType() == GeomAbs_Line) { + segment = createLinePrimitive(edge, document, "Segment"); + } + else if (adaptor.GetType() == GeomAbs_Circle) { + auto* arc = createCirclePrimitive(edge, document, "Arc"); + segment = arc; + } + if (segment) { + IncrementCreatedObjectCount(); + segment->Visibility.setValue(false); + ApplyGuiStyles(static_cast(segment)); + segments.push_back(segment); + } + } + p->Links.setValues(segments); + newObject = p; + break; + } + case GeometryBuilder::PrimitiveType::Spline: + case GeometryBuilder::PrimitiveType::Ellipse: + default: + // Fallback for types without a specific primitive + auto* p = document->addObject("Shape"); + p->Shape.setValue(builder.shape); + newObject = p; + break; + } + + if (newObject) { IncrementCreatedObjectCount(); - geomFeature->Shape.setValue(shape); - geomFeature->Visibility.setValue(false); - - // Apply styling to this primitive feature using its original attributes. - this->m_entityAttributes = attributes; - this->ApplyGuiStyles(geomFeature); - - linkedObjects.push_back(geomFeature); + newObject->Visibility.setValue(false); + ApplyGuiStyles(static_cast(newObject)); + childObjects.push_back(newObject); } } } - // TODO: Add similar logic for blockData.FeatureBuildersList if needed. - // 6. Finalize the Part::Compound. - if (!linkedObjects.empty()) { - blockCompound->Links.setValues(linkedObjects); + if (!childObjects.empty()) { + blockCompound->Links.setValues(childObjects); } // 7. Mark this block as composed. @@ -487,7 +570,7 @@ void ImpExpDxfRead::ComposeBlocks() { std::set composedBlocks; - if (m_mergeOption == MergeShapes) { + if (m_importMode == ImportMode::FusedShapes) { // User wants flattened geometry for performance. for (const auto& pair : this->Blocks) { if (composedBlocks.find(pair.first) == composedBlocks.end()) { @@ -612,8 +695,7 @@ bool ImpExpDxfRead::OnReadBlock(const std::string& name, int flags) // The .emplace method is slightly more efficient here. auto& temporaryBlock = Blocks.emplace(std::make_pair(name, Block(name, flags))).first->second; BlockDefinitionCollector blockCollector(*this, - temporaryBlock.Shapes, - temporaryBlock.FeatureBuildersList, + temporaryBlock.GeometryBuilders, temporaryBlock.Inserts); if (!ReadBlockContents()) { return false; // Abort on parsing error @@ -632,24 +714,17 @@ void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start, return; } - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - gp_Pnt p0 = makePoint(start); - gp_Pnt p1 = makePoint(end); - // TODO: Really?? What about the people designing integrated circuits? - if (p0.IsEqual(p1, 1e-8)) { - return; - } - Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line"); - break; - } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - // Do nothing until these modes have been implemented, the one-time warning has already - // been issued. - break; + gp_Pnt p0 = makePoint(start); + gp_Pnt p1 = makePoint(end); + if (p0.IsEqual(p1, 1e-8)) { + return; } + TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(p0, p1).Edge(); + GeometryBuilder builder(edge); + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Line; + } + Collector->AddGeometry(builder); } @@ -658,20 +733,13 @@ void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start) if (shouldSkipEntity()) { return; } + TopoDS_Vertex vertex = BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(); + GeometryBuilder builder(vertex); - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - // For non-parametric modes, create a Part::Feature with a Vertex shape. - Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point"); - break; - } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - // Do nothing until these modes have been implemented, the one-time warning has already - // been issued. - break; + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Point; } + Collector->AddGeometry(builder); } @@ -685,31 +753,25 @@ void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start, return; } - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - gp_Pnt p0 = makePoint(start); - gp_Pnt p1 = makePoint(end); - gp_Dir up(0, 0, 1); - if (!dir) { - up.Reverse(); - } - gp_Pnt pc = makePoint(center); - gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); - if (circle.Radius() > 1e-9) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n"); - } - break; - } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - // Do nothing until these modes have been implemented, the one-time warning has already - // been issued. - break; + gp_Pnt p0 = makePoint(start); + gp_Pnt p1 = makePoint(end); + gp_Dir up(0, 0, 1); + if (!dir) { + up.Reverse(); } + gp_Pnt pc = makePoint(center); + gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); + if (circle.Radius() < 1e-9) { + Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n"); + return; + } + + TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); + GeometryBuilder builder(edge); + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Arc; + } + Collector->AddGeometry(builder); } @@ -722,28 +784,24 @@ void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start, return; } - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - gp_Pnt p0 = makePoint(start); - gp_Dir up(0, 0, 1); - if (!dir) { - up.Reverse(); - } - gp_Pnt pc = makePoint(center); - gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); - if (circle.Radius() > 1e-9) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate circle\n"); - } - break; - } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - break; + gp_Pnt p0 = makePoint(start); + gp_Dir up(0, 0, 1); + if (!dir) { + up.Reverse(); } + gp_Pnt pc = makePoint(center); + gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); + if (circle.Radius() < 1e-9) { + Base::Console().warning("ImpExpDxf - ignore degenerate circle\n"); + return; + } + + TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circle).Edge(); + GeometryBuilder builder(edge); + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Circle; + } + Collector->AddGeometry(builder); } @@ -847,32 +905,25 @@ void ImpExpDxfRead::OnReadSpline(struct SplineData& sd) return; } - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - try { - Handle(Geom_BSplineCurve) geom; - if (sd.control_points > 0) { - geom = getSplineFromPolesAndKnots(sd); - } - else if (sd.fit_points > 0) { - geom = getInterpolationSpline(sd); - } - - if (geom.IsNull()) { - throw Standard_Failure(); - } - - Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline"); - } - catch (const Standard_Failure&) { - Base::Console().warning("ImpExpDxf - failed to create bspline\n"); - } - break; + try { + Handle(Geom_BSplineCurve) geom; + if (sd.control_points > 0) { + geom = getSplineFromPolesAndKnots(sd); } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - break; + else if (sd.fit_points > 0) { + geom = getInterpolationSpline(sd); + } + + if (!geom.IsNull()) { + GeometryBuilder builder(BRepBuilderAPI_MakeEdge(geom).Edge()); + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Spline; + } + Collector->AddGeometry(builder); + } + } + catch (const Standard_Failure&) { + Base::Console().warning("ImpExpDxf - failed to create bspline\n"); } } @@ -890,28 +941,23 @@ void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center, return; } - switch (m_importMode) { - case ImportMode::IndividualShapes: - case ImportMode::FusedShapes: { - gp_Dir up(0, 0, 1); - if (!dir) { - up.Reverse(); - } - gp_Pnt pc = makePoint(center); - gp_Elips ellipse(gp_Ax2(pc, up), major_radius, minor_radius); - ellipse.Rotate(gp_Ax1(pc, up), rotation); - if (ellipse.MinorRadius() > 1e-9) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n"); - } - break; - } - case ImportMode::EditableDraft: - case ImportMode::EditablePrimitives: - break; + gp_Dir up(0, 0, 1); + if (!dir) { + up.Reverse(); } + gp_Pnt pc = makePoint(center); + gp_Elips ellipse(gp_Ax2(pc, up), major_radius, minor_radius); + ellipse.Rotate(gp_Ax1(pc, up), rotation); + if (ellipse.MinorRadius() < 1e-9) { + Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n"); + return; + } + + GeometryBuilder builder(BRepBuilderAPI_MakeEdge(ellipse).Edge()); + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::Ellipse; + } + Collector->AddGeometry(builder); } void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, @@ -919,41 +965,36 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, const std::string& text, const double rotation) { - if (shouldSkipEntity()) { + if (shouldSkipEntity() || !m_importAnnotations) { return; } - // Note that our parameters do not contain all the information needed to properly orient the - // text. As a result the text will always appear on the XY plane - if (m_importAnnotations) { - auto makeText = [this, rotation, point, text, height]( - const Base::Matrix4D& transform) -> App::FeaturePython* { - PyObject* draftModule = getDraftModule(); - if (draftModule != nullptr) { - Base::Matrix4D localTransform; - localTransform.rotZ(Base::toRadians(rotation)); - localTransform.move(point); - PyObject* placement = - new Base::PlacementPy(Base::Placement(transform * localTransform)); - // returns a wrapped App::FeaturePython - auto builtText = dynamic_cast*>( - // NOLINTNEXTLINE(readability/nolint) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - (Base::PyObjectBase*)PyObject_CallMethod(draftModule, - "make_text", - "sOif", - text.c_str(), - placement, - 0, - height)); - Py_DECREF(placement); - if (builtText != nullptr) { - return dynamic_cast(builtText->getDocumentObjectPtr()); - } - } - return nullptr; - }; - Collector->AddObject((FeaturePythonBuilder)makeText); + auto* p = static_cast(document->addObject("App::FeaturePython", "Text")); + if (p) { + p->addDynamicProperty("App::PropertyString", + "DxfEntityType", + "Internal", + "DXF entity type"); + static_cast(p->getPropertyByName("DxfEntityType"))->setValue("TEXT"); + + p->addDynamicProperty("App::PropertyStringList", "Text", "Data", "Text content"); + // Explicitly create the vector to resolve ambiguity + std::vector text_values = {text}; + static_cast(p->getPropertyByName("Text"))->setValues(text_values); + + p->addDynamicProperty("App::PropertyFloat", + "DxfTextHeight", + "Internal", + "Original text height"); + static_cast(p->getPropertyByName("DxfTextHeight"))->setValue(height); + + p->addDynamicProperty("App::PropertyPlacement", "Placement", "Base", "Object placement"); + Base::Placement pl; + pl.setPosition(point); + pl.setRotation(Base::Rotation(Base::Vector3d(0, 0, 1), Base::toRadians(rotation))); + static_cast(p->getPropertyByName("Placement"))->setValue(pl); + + Collector->AddObject(p, "Text"); } } @@ -979,82 +1020,150 @@ void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start, const Base::Vector3d& point, double /*rotation*/) { - if (shouldSkipEntity()) { + if (shouldSkipEntity() || !m_importAnnotations) { return; } - if (m_importAnnotations) { - auto makeDimension = - [this, start, end, point](const Base::Matrix4D& transform) -> App::FeaturePython* { - PyObject* draftModule = getDraftModule(); - if (draftModule != nullptr) { - // TODO: Capture and apply OCSOrientationTransform to OCS coordinates - // Note, some of the locations in the DXF are OCS and some are UCS, but UCS - // doesn't mean UCS when in a block expansion, it means 'transform' So we want - // transform*vector for "UCS" coordinates and transform*ocdCapture*vector for - // "OCS" coordinates - // - // We implement the transform by mapping all the points from OCS to UCS - // TODO: Set the Normal property to transform*(0,0,1,0) - // TODO: Set the Direction property to transform*(the desired direction). - // By default this is parallel to (start-end). - PyObject* startPy = new Base::VectorPy(transform * start); - PyObject* endPy = new Base::VectorPy(transform * end); - PyObject* lineLocationPy = new Base::VectorPy(transform * point); - // returns a wrapped App::FeaturePython - auto builtDim = dynamic_cast*>( - // NOLINTNEXTLINE(readability/nolint) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - (Base::PyObjectBase*)PyObject_CallMethod(draftModule, - "make_linear_dimension", - "OOO", - startPy, - endPy, - lineLocationPy)); - Py_DECREF(startPy); - Py_DECREF(endPy); - Py_DECREF(lineLocationPy); - if (builtDim != nullptr) { - return dynamic_cast(builtDim->getDocumentObjectPtr()); - } - } - return nullptr; - }; - Collector->AddObject((FeaturePythonBuilder)makeDimension); + auto* p = + static_cast(document->addObject("App::FeaturePython", "Dimension")); + if (p) { + p->addDynamicProperty("App::PropertyString", + "DxfEntityType", + "Internal", + "DXF entity type"); + static_cast(p->getPropertyByName("DxfEntityType")) + ->setValue("DIMENSION"); + + p->addDynamicProperty("App::PropertyVector", "Start", "Data", "Start point of dimension"); + static_cast(p->getPropertyByName("Start"))->setValue(start); + + p->addDynamicProperty("App::PropertyVector", "End", "Data", "End point of dimension"); + static_cast(p->getPropertyByName("End"))->setValue(end); + + p->addDynamicProperty("App::PropertyVector", "Dimline", "Data", "Point on dimension line"); + static_cast(p->getPropertyByName("Dimline"))->setValue(point); + + p->addDynamicProperty("App::PropertyPlacement", "Placement", "Base", "Object placement"); + Base::Placement pl; + // Correctly construct the rotation directly from the 4x4 matrix. + // The Base::Rotation constructor will extract the rotational part. + pl.setRotation(Base::Rotation(OCSOrientationTransform)); + static_cast(p->getPropertyByName("Placement"))->setValue(pl); + + Collector->AddObject(p, "Dimension"); } } + void ImpExpDxfRead::OnReadPolyline(std::list& vertices, int flags) { if (shouldSkipEntity()) { return; } - // Polyline explosion logic is complex and calls back to other OnRead... handlers. - // The mode switch should happen inside the final geometry creation handlers - // (OnReadLine, OnReadArc), so this function doesn't need its own switch statement. - // It simply acts as a dispatcher. - - std::map> ShapesToCombine; - { - // TODO: Currently ExpandPolyline calls OnReadArc etc to generate the pieces, and these - // create TopoShape objects which ShapeSavingEntityCollector can gather up. - // Eventually when m_mergeOption being DraftObjects is implemented OnReadArc etc might - // generate Draft objects which ShapeSavingEntityCollector does not save. - // We need either a collector that collects everything (and we have to figure out - // how to join Draft objects) or we need to temporarily set m_mergeOption to - // SingleShapes if it is set to DraftObjects (and safely restore it on exceptions) A - // clean way would be to give the collector a "makeDraftObjects" property, and our - // special collector could give this the value 'false' whereas the main collector would - // base this on the option setting. Also ShapeSavingEntityCollector classifies by - // entityAttributes which is not needed here because they are constant throughout. - ShapeSavingEntityCollector savingCollector(*this, ShapesToCombine); - ExplodePolyline(vertices, flags); + if (vertices.size() < 2 && (flags & 1) == 0) { + return; // Not enough vertices for an open polyline } - // Join the shapes. - if (!ShapesToCombine.empty()) { - // TODO: If we want Draft objects and all segments are straight lines we can make a - // draft wire. - CombineShapes(ShapesToCombine.begin()->second, "Polyline"); + + BRepBuilderAPI_MakeWire wireBuilder; + bool is_closed = ((flags & 1) != 0); + auto it = vertices.begin(); + auto prev_it = it++; + + while (it != vertices.end()) { + const VertexInfo& start_vertex = *prev_it; + const VertexInfo& end_vertex = *it; + TopoDS_Edge edge; + + if (start_vertex.bulge == 0.0) { + edge = BRepBuilderAPI_MakeEdge(makePoint(start_vertex.location), + makePoint(end_vertex.location)) + .Edge(); + } + else { + double cot = ((1.0 / start_vertex.bulge) - start_vertex.bulge) / 2.0; + double center_x = ((start_vertex.location.x + end_vertex.location.x) + - (end_vertex.location.y - start_vertex.location.y) * cot) + / 2.0; + double center_y = ((start_vertex.location.y + end_vertex.location.y) + + (end_vertex.location.x - start_vertex.location.x) * cot) + / 2.0; + double center_z = (start_vertex.location.z + end_vertex.location.z) / 2.0; + Base::Vector3d center(center_x, center_y, center_z); + + gp_Pnt p0 = makePoint(start_vertex.location); + gp_Pnt p1 = makePoint(end_vertex.location); + gp_Dir up(0, 0, 1); + if (start_vertex.bulge < 0) { + up.Reverse(); + } + gp_Pnt pc = makePoint(center); + gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); + if (circle.Radius() > 1e-9) { + edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); + } + } + + if (!edge.IsNull()) { + wireBuilder.Add(edge); + } + + prev_it = it++; + } + + if (is_closed && vertices.size() > 1) { + const VertexInfo& start_vertex = vertices.back(); + const VertexInfo& end_vertex = vertices.front(); + TopoDS_Edge edge; + + if (start_vertex.bulge == 0.0) { + edge = BRepBuilderAPI_MakeEdge(makePoint(start_vertex.location), + makePoint(end_vertex.location)) + .Edge(); + } + else { + double cot = ((1.0 / start_vertex.bulge) - start_vertex.bulge) / 2.0; + double center_x = ((start_vertex.location.x + end_vertex.location.x) + - (end_vertex.location.y - start_vertex.location.y) * cot) + / 2.0; + double center_y = ((start_vertex.location.y + end_vertex.location.y) + + (end_vertex.location.x - start_vertex.location.x) * cot) + / 2.0; + double center_z = (start_vertex.location.z + end_vertex.location.z) / 2.0; + Base::Vector3d center(center_x, center_y, center_z); + + gp_Pnt p0 = makePoint(start_vertex.location); + gp_Pnt p1 = makePoint(end_vertex.location); + gp_Dir up(0, 0, 1); + if (start_vertex.bulge < 0) { + up.Reverse(); + } + gp_Pnt pc = makePoint(center); + gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); + if (circle.Radius() > 1e-9) { + edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); + } + } + if (!edge.IsNull()) { + wireBuilder.Add(edge); + } + } + + if (wireBuilder.IsDone()) { + TopoDS_Wire wire = wireBuilder.Wire(); + GeometryBuilder builder(wire); + + // For FusedShapes mode, we can create the object immediately. + // For other modes, we store the builder for later processing. + if (m_importMode == ImportMode::FusedShapes) { + Collector->AddObject(wire, "Polyline"); + return; + } + + if (m_importMode == ImportMode::EditablePrimitives) { + builder.type = GeometryBuilder::PrimitiveType::PolylineCompound; + } + + Collector->AddGeometry(builder); } } diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.h b/src/Mod/Import/App/dxf/ImpExpDxf.h index bdf38a8623..002645f811 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.h +++ b/src/Mod/Import/App/dxf/ImpExpDxf.h @@ -117,6 +117,29 @@ public: void FinishImport() override; private: + class GeometryBuilder + { + public: + // The type of primitive that a shape represents. 'None' is used for + // non-parametric modes. + enum class PrimitiveType + { + None, + Point, + Line, + Circle, + Arc, + Ellipse, + Spline, + PolylineCompound + }; + + // The raw geometric shape. + TopoDS_Shape shape; + // The intended parametric type for the shape. + PrimitiveType type = PrimitiveType::None; + }; + ImportMode m_importMode = ImportMode::IndividualShapes; bool shouldSkipEntity() const { @@ -207,9 +230,7 @@ protected: {} const std::string Name; const int Flags; - std::map> Shapes; - std::map> - FeatureBuildersList; + std::map> GeometryBuilders; std::map> Inserts; }; @@ -259,6 +280,8 @@ protected: // Called by OnReadXxxx functions to add Part objects virtual void AddObject(const TopoDS_Shape& shape, const char* nameBase) = 0; + // Generic method to add a new geometry builder + virtual void AddGeometry(const GeometryBuilder& builder) = 0; // Called by OnReadInsert to add App::Link or other C++-created objects virtual void AddObject(App::DocumentObject* obj, const char* nameBase) = 0; // Called by OnReadXxxx functions to add FeaturePython (draft) objects. @@ -288,6 +311,12 @@ protected: {} void AddObject(const TopoDS_Shape& shape, const char* nameBase) override; + void AddGeometry(const GeometryBuilder& builder) override + { + // In drawing mode, we create objects immediately based on the builder. + // For now, this just creates simple shapes. Primitives would need more logic here. + AddObject(builder.shape, "Shape"); + } void AddObject(App::DocumentObject* obj, const char* nameBase) override; void AddObject(FeaturePythonBuilder shapeBuilder) override; void AddInsert(const Base::Vector3d& point, @@ -351,6 +380,11 @@ protected: ShapesList[Reader.m_entityAttributes].push_back(shape); } + void AddGeometry(const GeometryBuilder& builder) override + { + ShapesList[Reader.m_entityAttributes].push_back(builder.shape); + } + void AddObject(App::DocumentObject* obj, const char* nameBase) override { // A Link is not a shape to be merged, so pass to base class for standard handling. @@ -388,33 +422,40 @@ protected: public: BlockDefinitionCollector( ImpExpDxfRead& reader, - std::map>& shapesList, - std::map>& - featureBuildersList, + std::map>& buildersList, std::map>& insertsList) : EntityCollector(reader) - , ShapesList(shapesList) - , FeatureBuildersList(featureBuildersList) + , BuildersList(buildersList) , InsertsList(insertsList) {} // TODO: We will want AddAttributeDefinition as well. void AddObject(const TopoDS_Shape& shape, const char* /*nameBase*/) override { - ShapesList[Reader.m_entityAttributes].push_back(shape); - } - void AddObject(FeaturePythonBuilder shapeBuilder) override - { - FeatureBuildersList[Reader.m_entityAttributes].push_back(shapeBuilder); + // This path should no longer be taken, but is kept for compatibility. + BuildersList[Reader.m_entityAttributes].emplace_back(GeometryBuilder(shape)); } - void AddObject(App::DocumentObject* /*obj*/, const char* /*nameBase*/) override + void AddGeometry(const GeometryBuilder& builder) override + { + BuildersList[Reader.m_entityAttributes].push_back(builder); + } + + void AddObject(App::DocumentObject* /*obj*/, const char* nameBase) override { // This path should never be executed. Links and other fully-formed DocumentObjects // are created from INSERT entities, not as part of a BLOCK *definition*. If this // warning ever appears, it indicates a logic error in the importer. Reader.ImportError( - "Internal logic error: Attempted to add a DocumentObject to a block definition."); + "Internal logic error: Attempted to add a DocumentObject ('%s') to a block " + "definition.\n", + nameBase); + } + + void AddObject(FeaturePythonBuilder /*shapeBuilder*/) override + { + // This path is for Draft/FeaturePython objects and is not used by the + // primitives or shapes modes. } void AddInsert(const Base::Vector3d& point, @@ -427,9 +468,7 @@ protected: } private: - std::map>& ShapesList; - std::map>& - FeatureBuildersList; + std::map>& BuildersList; std::map>& InsertsList; }; @@ -507,4 +546,4 @@ protected: } // namespace Import -#endif // IMPEXPDXF_H +#endif // IMPEXPDXFGUI_H