From 37cd1a657a849b27ca5e78b231d7471163724c75 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:09:26 +0200 Subject: [PATCH] Import: DXF importer, refactor preferences UI --- src/Mod/Draft/Resources/ui/preferences-dxf.ui | 774 +++++++++--------- src/Mod/Draft/importDXF.py | 81 +- src/Mod/Import/App/dxf/ImpExpDxf.cpp | 271 +++--- src/Mod/Import/App/dxf/ImpExpDxf.h | 11 +- 4 files changed, 614 insertions(+), 523 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/preferences-dxf.ui b/src/Mod/Draft/Resources/ui/preferences-dxf.ui index 45b348d707..c4a78612cd 100644 --- a/src/Mod/Draft/Resources/ui/preferences-dxf.ui +++ b/src/Mod/Draft/Resources/ui/preferences-dxf.ui @@ -6,27 +6,28 @@ 0 0 - 649 - 800 + 600 + 880 DXF - + - + - General options + General - + - This preferences dialog will be shown when importing/ exporting DXF files + If checked, this preferences dialog will be shown each time you import or export +a DXF file. - Show this dialog when importing and exporting + Show the importer dialog when importing a file true @@ -42,14 +43,10 @@ - Python importer is used, otherwise the newer C++ is used. -Note: C++ importer is faster, but is not as featureful yet + Use the legacy Python importer. This importer is more feature-complete but slower and requires an external library. - Use legacy Python importer - - - false + Use legacy importer dxfUseLegacyImporter @@ -62,11 +59,10 @@ Note: C++ importer is faster, but is not as featureful yet - Python exporter is used, otherwise the newer C++ is used. -Note: C++ exporter is faster, but is not as featureful yet + Use the legacy Python exporter. This exporter is more feature-complete but slower and requires an external library. - Use legacy Python exporter + Use legacy exporter dxfUseLegacyExporter @@ -80,227 +76,160 @@ Note: C++ exporter is faster, but is not as featureful yet - + Automatic update (legacy importer/exporter only) - + - - - - - Allow FreeCAD to download the Python converter for DXF import and export. -You can also do this manually by installing the "dxf_library" workbench -from the Addon Manager. - - - Allow FreeCAD to automatically download and update the DXF libraries - - - dxfAllowDownload - - - Mod/Draft - - - - + + + If checked, FreeCAD is allowed to download and update the Python libraries +required by the legacy importer. This can also be done manually by installing +the 'dxf_library' addon from the Addon Manager. + + + Allow FreeCAD to automatically download and update the DXF libraries + + + dxfAllowDownload + + + Mod/Draft + + - + - Import options + Import as - - - 6 - - - 9 - - - 9 - - - 9 - - - 9 - + - - - - true - + + + false + + + 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) - Some options are not yet available for the new importer + Editable draft objects (Highest fidelity, slowest) + + + DxfImportMode + + + Mod/Draft + + + DxfImportMode + + + 0 - - - - - Import - - - - - - - If unchecked, texts and mtexts won't be imported - - - Texts and dimensions - - - dxftext - - - Mod/Draft - - - - - - - If unchecked, points won't be imported - - - points - - - dxfImportPoints - - - Mod/Draft - - - - - - - If checked, paper space objects will be imported too - - - Layouts - - - dxflayout - - - Mod/Draft - - - - - - - If you want the non-named blocks (beginning with a *) to be imported too - - - *blocks - - - dxfstarblocks - - - Mod/Draft - - - - + + + false + + + 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) + + + Editable part primitives (High fidelity, slower) + + + DxfImportMode + + + Mod/Draft + + + DxfImportMode + + + 1 + + - - - - - false - - - Create - - - - - - - false - - - Only standard Part objects will be created (fastest) - - - Simple Part shapes - - - true - - - dxfCreatePart - - - Mod/Draft - - - - - - - false - - - Parametric Draft objects will be created whenever possible - - - Draft objects - - - dxfCreateDraft - - - Mod/Draft - - - - - - - false - - - Sketches will be created whenever possible - - - Sketches - - - dxfCreateSketch - - - Mod/Draft - - - - + + + Creates a non-parametric shape for each DXF entity. Block definitions are +imported as reusable objects (Part Compounds) and instances become `App::Link` +objects, maintaining the block structure. Good for referencing and measuring. + + + Individual part shapes (Balanced, recommended) + + + true + + + DxfImportMode + + + Mod/Draft + + + DxfImportMode + + + 2 + + - + + + 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. + + + Fused part shapes (Lowest fidelity, fastest) + + + DxfImportMode + + + Mod/Draft + + + DxfImportMode + + + 3 + + + + + + + + + + Import settings + + + + - - - - Scale factor to apply to imported files + Global scaling factor: @@ -320,13 +249,13 @@ from the Addon Manager. - Scale factor to apply to DXF files on import. -The factor is the conversion between the unit of your DXF file and millimeters. -Example: for files in millimeters: 1, in centimeters: 10, - in meters: 1000, in inches: 25.4, in feet: 304.8 + Scale factor to apply to DXF files on import. The factor is the conversion +between the unit of your DXF file and millimeters. Example: for files in +millimeters: 1, in centimeters: 10, in meters: 1000, in inches: 25.4, +in feet: 304.8 - 12 + 6 999999.999998999992386 @@ -345,16 +274,126 @@ Example: for files in millimeters: 1, in centimeters: 10, - - + + + Import: + + + + + + + + + If checked, text, mtext, and dimension entities will be imported as Draft objects. + + + Texts and dimensions + + + dxftext + + + Mod/Draft + + + + + + + If checked, point entities will be imported. + + + Points + + + true + + + dxfImportPoints + + + Mod/Draft + + + + + + + If checked, entities from the paper space will also be imported. By default, +only model space is imported. + + + Paper space objects + + + dxflayout + + + Mod/Draft + + + + + + + If checked, anonymous blocks (whose names begin with *) will also be imported. +These are often used for hatches and dimensions. + + + Anonymous blocks (*-blocks) + + + dxfstarblocks + + + Mod/Draft + + + + + + + false + + + If checked, the boundaries of hatch objects will be imported as closed wires. +(Legacy importer only) + + + Hatch boundaries + + + importDxfHatches + + + Mod/Draft + + + + + + + + + Appearance: + + + + + + - Colors will set as specified in the DXF file whenever possible. -Otherwise default colors will be applied. + If checked, colors will be set as specified in the DXF file whenever +possible. Otherwise, default FreeCAD colors are applied. Use colors from the DXF file + + true + dxfGetOriginalColors @@ -363,63 +402,14 @@ Otherwise default colors will be applied. - - - - - - - - false - - - FreeCAD will try to join coincident objects into wires. -Note that this can take a while! - - - Join geometry - - - joingeometry - - - Mod/Draft - - - - - - - - - - - Objects from the same layers will be joined into Part Compounds, -turning the display faster, but making them less easily editable. - - - Merge layer contents into blocks - - - groupLayers - - - Mod/Draft - - - - - - - - + false - Imported texts will get the standard Draft Text size, -instead of the size they have in the DXF document + If checked, imported texts will get the standard Draft Text size, instead of +the size defined in the DXF document. (Legacy importer only) Use standard font size for texts @@ -435,61 +425,42 @@ instead of the size they have in the DXF document - - - - - If this is checked, DXF layers will be imported as Draft Layers - - - Use layers - - - true - - - dxfUseDraftVisGroups - - - Mod/Draft - - - - + + + Advanced processing: + + - - - + + + false - Hatches will be converted into simple wires + If checked, the legacy importer will attempt to join coincident geometric +objects into wires. This can be slow for large files. (Legacy importer only) - Import hatch boundaries as wires + Join geometry - importDxfHatches + joingeometry Mod/Draft - - - - - + false - If polylines have a width defined, they will be rendered -as closed wires with correct width + If checked, polylines that have a width property will be rendered as faces +representing that width. (Legacy importer only) Render polylines with width @@ -502,31 +473,39 @@ as closed wires with correct width + + + + false + + + If checked, the legacy importer will attempt to create Sketcher objects +instead of Draft or Part objects. This overrides the 'Import as' setting. + + + Create sketches (legacy importer only) + + + dxfCreateSketch + + + Mod/Draft + + + - + Export options - + - - - - true - - - - Some options are not yet available for the new exporter - - - - - + @@ -598,7 +577,7 @@ If it is set to '0' the whole spline is treated as a straight segment. - + @@ -621,7 +600,7 @@ If it is set to '0' the whole spline is treated as a straight segment. - + @@ -645,7 +624,7 @@ This might fail for post DXF R12 templates. - + @@ -671,21 +650,20 @@ This might fail for post DXF R12 templates. - + Qt::Vertical 20 - 40 + 0 - Gui::PrefCheckBox @@ -693,82 +671,18 @@ This might fail for post DXF R12 templates.
Gui/PrefWidgets.h
- Gui::PrefDoubleSpinBox - QDoubleSpinBox + Gui::PrefRadioButton + QRadioButton
Gui/PrefWidgets.h
- Gui::PrefRadioButton - QRadioButton + Gui::PrefDoubleSpinBox + QDoubleSpinBox
Gui/PrefWidgets.h
- - checkBox_dxfUseLegacyImporter - toggled(bool) - label_Create - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - checkBox_dxfUseLegacyImporter - toggled(bool) - radioButton_dxfCreatePart - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - checkBox_dxfUseLegacyImporter - toggled(bool) - radioButton_dxfCreateDraft - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - checkBox_dxfUseLegacyImporter - toggled(bool) - radioButton_dxfCreateSketch - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - checkBox_dxfUseLegacyImporter toggled(bool) @@ -785,6 +699,22 @@ This might fail for post DXF R12 templates. + + checkBox_dxfUseLegacyImporter + toggled(bool) + checkBox_renderPolylineWidth + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + checkBox_dxfUseLegacyImporter toggled(bool) @@ -820,7 +750,55 @@ This might fail for post DXF R12 templates. checkBox_dxfUseLegacyImporter toggled(bool) - checkBox_renderPolylineWidth + checkBox_dxfCreateSketch + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + checkBox_dxfCreateSketch + toggled(bool) + groupBox_ImportAs + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + checkBox_dxfUseLegacyExporter + toggled(bool) + checkBox_dxfmesh + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + checkBox_dxfUseLegacyExporter + toggled(bool) + checkBox_dxfproject setEnabled(bool) diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index 1325611448..39fcc9c3a9 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -4163,6 +4163,8 @@ def getViewDXF(view): return block, insert +# In src/Mod/Draft/importDXF.py + def readPreferences(): """Read the preferences of the this module from the parameter database. @@ -4184,6 +4186,7 @@ def readPreferences(): # reading parameters if gui and params.get_param("dxfShowDialog"): FreeCADGui.showPreferencesByName("Import-Export", ":/ui/preferences-dxf.ui") + global dxfCreatePart, dxfCreateDraft, dxfCreateSketch global dxfDiscretizeCurves, dxfStarBlocks global dxfMakeBlocks, dxfJoin, dxfRenderPolylineWidth @@ -4193,28 +4196,66 @@ def readPreferences(): global dxfMakeFaceMode, dxfBrightBackground, dxfDefaultColor global dxfUseLegacyImporter, dxfExportBlocks, dxfScaling global dxfUseLegacyExporter - dxfCreatePart = params.get_param("dxfCreatePart") - dxfCreateDraft = params.get_param("dxfCreateDraft") - dxfCreateSketch = params.get_param("dxfCreateSketch") - dxfDiscretizeCurves = params.get_param("DiscretizeEllipses") - dxfStarBlocks = params.get_param("dxfstarblocks") - dxfMakeBlocks = params.get_param("groupLayers") - dxfJoin = params.get_param("joingeometry") - dxfRenderPolylineWidth = params.get_param("renderPolylineWidth") - dxfImportTexts = params.get_param("dxftext") - dxfImportLayouts = params.get_param("dxflayout") - dxfImportPoints = params.get_param("dxfImportPoints") - dxfImportHatches = params.get_param("importDxfHatches") - dxfUseStandardSize = params.get_param("dxfStdSize") - dxfGetColors = params.get_param("dxfGetOriginalColors") - dxfUseDraftVisGroups = params.get_param("dxfUseDraftVisGroups") - dxfMakeFaceMode = params.get_param("MakeFaceMode") - dxfUseLegacyImporter = params.get_param("dxfUseLegacyImporter") - dxfUseLegacyExporter = params.get_param("dxfUseLegacyExporter") + + # --- Read all feature and appearance toggles --- + # These are independent settings and can be read directly. + dxfDiscretizeCurves = params.get_param("DiscretizeEllipses", False) + dxfStarBlocks = params.get_param("dxfstarblocks", False) + dxfJoin = params.get_param("joingeometry", False) + dxfRenderPolylineWidth = params.get_param("renderPolylineWidth", False) + dxfImportTexts = params.get_param("dxftext", True) + dxfImportLayouts = params.get_param("dxflayout", False) + dxfImportPoints = params.get_param("dxfImportPoints", True) + dxfImportHatches = params.get_param("importDxfHatches", True) + dxfUseStandardSize = params.get_param("dxfStdSize", False) + dxfGetColors = params.get_param("dxfGetOriginalColors", True) + dxfUseDraftVisGroups = params.get_param("dxfUseDraftVisGroups", True) + dxfMakeFaceMode = params.get_param("MakeFaceMode", False) + dxfExportBlocks = params.get_param("dxfExportBlocks", True) + dxfScaling = params.get_param("dxfScaling", 1.0) + + # These control which importer is used. The script should only proceed if + # the legacy importer is selected. + dxfUseLegacyImporter = params.get_param("dxfUseLegacyImporter", False) + dxfUseLegacyExporter = params.get_param("dxfUseLegacyExporter", False) + + if not dxfUseLegacyImporter: + # If the legacy importer is called when not selected, exit. + # This prevents accidental execution. + return + + # --- New, Centralized Logic for Structural Mode --- + + # Read the legacy-specific override for sketch creation. + dxfCreateSketch = params.get_param("dxfCreateSketch", False) + + if dxfCreateSketch: + # Sketch mode takes highest priority, other modes are irrelevant. + dxfCreatePart = False + dxfCreateDraft = False + dxfMakeBlocks = False + else: + # Not in sketch mode, so determine structure from DxfImportMode. + # This is where the new parameter is read, with its default value defined. + # 0=Draft, 1=Primitives, 2=Shapes, 3=Fused + import_mode = params.get_param("DxfImportMode", 2) # Default to "Individual part shapes" + + if import_mode == 3: # Fused part shapes + dxfMakeBlocks = True # 'groupLayers' is the legacy equivalent + dxfCreatePart = False # In legacy, dxfMakeBlocks overrides these + dxfCreateDraft = False + elif import_mode == 0: # Editable draft objects + dxfMakeBlocks = False + dxfCreatePart = False + dxfCreateDraft = True + else: # Covers modes 1 (Primitives) and 2 (Shapes). Legacy maps both to "Simple part shapes" + dxfMakeBlocks = False + dxfCreatePart = True + dxfCreateDraft = False + + # --- Other settings that are not checkboxes --- dxfBrightBackground = isBrightBackground() dxfDefaultColor = getColor() - dxfExportBlocks = params.get_param("dxfExportBlocks") - dxfScaling = params.get_param("dxfScaling") class DxfImportReporter: diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.cpp b/src/Mod/Import/App/dxf/ImpExpDxf.cpp index 7a6add1c19..3319fe27a2 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.cpp +++ b/src/Mod/Import/App/dxf/ImpExpDxf.cpp @@ -106,12 +106,23 @@ 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(); DrawingEntityCollector collector(*this); - if (m_mergeOption < SingleShapes) { + if (m_importMode == ImportMode::FusedShapes) { std::map> ShapesToCombine; { ShapeSavingEntityCollector savingCollector(*this, ShapesToCombine); @@ -186,31 +197,18 @@ void ImpExpDxfRead::setOptions() m_preserveColors = hGrp->GetBool("dxfGetOriginalColors", true); m_stats.importSettings["Use colors from the DXF file"] = m_preserveColors ? "Yes" : "No"; - // Default for creation type is to create draft objects. - // The radio-button structure of the options dialog should generally prevent this condition. - m_mergeOption = DraftObjects; - m_stats.importSettings["Merge option"] = "Create Draft objects"; // Default - if (hGrp->GetBool("groupLayers", true)) { - // Group all compatible objects together - m_mergeOption = MergeShapes; - m_stats.importSettings["Merge option"] = "Group layers into blocks"; - } - else if (hGrp->GetBool("dxfCreatePart", true)) { - // Create (non-draft) Shape objects when possible - m_mergeOption = SingleShapes; - m_stats.importSettings["Merge option"] = "Create Part shapes"; - } - else if (hGrp->GetBool("dxfCreateDraft", true)) { - // Create only Draft objects, making the result closest to drawn-from-scratch - m_mergeOption = DraftObjects; - m_stats.importSettings["Merge option"] = "Create Draft objects"; - } + // Read the new master import mode parameter, set the default. + int mode = hGrp->GetInt("DxfImportMode", static_cast(ImportMode::IndividualShapes)); + m_importMode = static_cast(mode); + // TODO: joingeometry should give an intermediate between MergeShapes and SingleShapes which // will merge shapes that happen to join end-to-end. As such it should be in the radio button // set, except that the legacy importer can do joining either for sketches or for shapes. What // this really means is there should be an "Import as sketch" checkbox, and only the // MergeShapes, JoinShapes, and SingleShapes radio buttons should be allowed, i.e. Draft Objects // would be ignored. + // Update: The "Join geometry" option is now a checkbox that is only enabled for the legacy + // importer. Whether the modern importer should support this is still up for debate. bool joinGeometry = hGrp->GetBool("joingeometry", false); m_stats.importSettings["Join geometry"] = joinGeometry ? "Yes" : "No"; @@ -604,13 +602,24 @@ void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start, return; } - gp_Pnt p0 = makePoint(start); - gp_Pnt p1 = makePoint(end); - if (p0.IsEqual(p1, 0.00000001)) { - // TODO: Really?? What about the people designing integrated circuits? - 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; } - Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line"); } @@ -620,7 +629,19 @@ void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start) return; } - Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point"); + 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; + } } @@ -634,19 +655,30 @@ void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start, return; } - gp_Pnt p0 = makePoint(start); - gp_Pnt p1 = makePoint(end); - gp_Dir up(0, 0, 1); - if (!dir) { - up = -up; - } - gp_Pnt pc = makePoint(center); - gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); - if (circle.Radius() > 0) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n"); + 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; } } @@ -660,18 +692,27 @@ void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start, return; } - gp_Pnt p0 = makePoint(start); - gp_Dir up(0, 0, 1); - if (!dir) { - up = -up; - } - gp_Pnt pc = makePoint(center); - gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); - if (circle.Radius() > 0) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate circle\n"); + 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; } } @@ -776,23 +817,32 @@ void ImpExpDxfRead::OnReadSpline(struct SplineData& sd) return; } - try { - Handle(Geom_BSplineCurve) geom; - if (sd.control_points > 0) { - geom = getSplineFromPolesAndKnots(sd); - } - else if (sd.fit_points > 0) { - geom = getInterpolationSpline(sd); - } + 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(); - } + 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"); + Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline"); + } + catch (const Standard_Failure&) { + Base::Console().warning("ImpExpDxf - failed to create bspline\n"); + } + break; + } + case ImportMode::EditableDraft: + case ImportMode::EditablePrimitives: + break; } } @@ -810,22 +860,30 @@ void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center, return; } - gp_Dir up(0, 0, 1); - if (!dir) { - up = -up; - } - 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() > 0) { - Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse"); - } - else { - Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n"); + 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; } } - void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, const double height, const std::string& text, @@ -901,10 +959,10 @@ void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start, 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 + // 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) @@ -941,6 +999,11 @@ void ImpExpDxfRead::OnReadPolyline(std::list& vertices, int flags) 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 @@ -948,20 +1011,19 @@ void ImpExpDxfRead::OnReadPolyline(std::list& vertices, int flags) // 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. + // 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); } // Join the shapes. if (!ShapesToCombine.empty()) { - // TODO: If we want Draft objects and all segments are straight lines we can make a draft - // wire. + // TODO: If we want Draft objects and all segments are straight lines we can make a + // draft wire. CombineShapes(ShapesToCombine.begin()->second, "Polyline"); } } @@ -1013,12 +1075,12 @@ ImpExpDxfRead::MakeLayer(const std::string& name, ColorIndex_t color, std::strin PyObject* layer = nullptr; draftModule = getDraftModule(); if (draftModule != nullptr) { - // After the colours, I also want to pass the draw_style, but there is an intervening - // line-width parameter. It is easier to just pass that parameter's default value than - // to do the handstands to pass a named parameter. + // After the colours, I also want to pass the draw_style, but there is an + // intervening line-width parameter. It is easier to just pass that parameter's + // default value than to do the handstands to pass a named parameter. // TODO: Pass the appropriate draw_style (from "Solid" "Dashed" "Dotted" "DashDot") - // This needs an ObjectDrawStyleName analogous to ObjectColor but at the ImpExpDxfGui - // level. + // This needs an ObjectDrawStyleName analogous to ObjectColor but at the + // ImpExpDxfGui level. layer = // NOLINTNEXTLINE(readability/nolint) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) @@ -1043,9 +1105,9 @@ ImpExpDxfRead::MakeLayer(const std::string& name, ColorIndex_t color, std::strin Py_False); } - // We make our own layer class even if we could not make a layer. MoveToLayer will ignore - // such layers but we have to do this because it is not a polymorphic type so we can't tell - // what we pull out of m_entityAttributes.m_Layer. + // We make our own layer class even if we could not make a layer. MoveToLayer will + // ignore such layers but we have to do this because it is not a polymorphic type so we + // can't tell what we pull out of m_entityAttributes.m_Layer. return result; } return CDxfRead::MakeLayer(name, color, std::move(lineType)); @@ -1055,8 +1117,8 @@ void ImpExpDxfRead::MoveToLayer(App::DocumentObject* object) const if (m_preserveLayers) { static_cast(m_entityAttributes.m_Layer)->Contents.push_back(object); } - // TODO: else Hide the object if it is in a Hidden layer? That won't work because we've cleared - // out m_entityAttributes.m_Layer + // TODO: else Hide the object if it is in a Hidden layer? That won't work because we've + // cleared out m_entityAttributes.m_Layer } @@ -1111,7 +1173,8 @@ void ImpExpDxfRead::DrawingEntityCollector::AddObject(App::DocumentObject* obj, const char* /*nameBase*/) { // This overload is for C++ created objects like App::Link - // The object is already in the document, so we just need to style it and move it to a layer. + // The object is already in the document, so we just need to style it and move it to a + // layer. Reader.MoveToLayer(obj); // Safely apply styles by checking the object's actual type diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.h b/src/Mod/Import/App/dxf/ImpExpDxf.h index 21b533d311..5a1855a651 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.h +++ b/src/Mod/Import/App/dxf/ImpExpDxf.h @@ -40,6 +40,15 @@ class BRepAdaptor_Curve; namespace Import { + +enum class ImportMode +{ + EditableDraft, + EditablePrimitives, + IndividualShapes, + FusedShapes +}; + class ImportExport ImpExpDxfRead: public CDxfRead { public: @@ -108,6 +117,7 @@ public: void FinishImport() override; private: + ImportMode m_importMode = ImportMode::IndividualShapes; bool shouldSkipEntity() const { // This entity is in paper space, and the user setting says to ignore it. @@ -370,7 +380,6 @@ protected: private: const EntityCollector* previousEntityCollector; - const eEntityMergeType_t previousMmergeOption; }; #endif class BlockDefinitionCollector: public EntityCollector