diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index 2f458b7ab6..b6d94016f9 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -54,6 +54,7 @@ import sys import os import math import re +import time import FreeCAD import Part import Draft @@ -2810,6 +2811,8 @@ def open(filename): Use local variables, not global variables. """ readPreferences() + total_start_time = time.perf_counter() + if dxfUseLegacyImporter: getDXFlibs() if dxfReader: @@ -2825,12 +2828,19 @@ def open(filename): doc = FreeCAD.newDocument(docname) doc.Label = docname FreeCAD.setActiveDocument(doc.Name) + stats = None if gui: import ImportGui - ImportGui.readDXF(filename) + stats = ImportGui.readDXF(filename) else: import Import - Import.readDXF(filename) + stats = Import.readDXF(filename) + + total_end_time = time.perf_counter() + if stats: + reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time) + reporter.report_to_console() + Draft.convert_draft_texts() # convert annotations to Draft texts doc.recompute() @@ -2855,6 +2865,7 @@ def insert(filename, docname): Use local variables, not global variables. """ readPreferences() + total_start_time = time.perf_counter() try: doc = FreeCAD.getDocument(docname) except NameError: @@ -2867,12 +2878,19 @@ def insert(filename, docname): else: errorDXFLib(gui) else: + stats = None if gui: import ImportGui - ImportGui.readDXF(filename) + stats = ImportGui.readDXF(filename) else: import Import - Import.readDXF(filename) + stats = Import.readDXF(filename) + + total_end_time = time.perf_counter() + if stats: + reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time) + reporter.report_to_console() + Draft.convert_draft_texts() # convert annotations to Draft texts doc.recompute() @@ -4197,3 +4215,122 @@ def readPreferences(): dxfDefaultColor = getColor() dxfExportBlocks = params.get_param("dxfExportBlocks") dxfScaling = params.get_param("dxfScaling") + + +class DxfImportReporter: + """Formats and reports statistics from a DXF import process.""" + def __init__(self, filename, stats_dict, total_time=0.0): + self.filename = filename + self.stats = stats_dict + self.total_time = total_time + + def to_console_string(self): + """ + Formats the statistics into a human-readable string for console output. + """ + if not self.stats: + return "DXF Import: no statistics were returned from the importer.\n" + + lines = ["\n--- DXF import summary ---"] + lines.append(f"Import of file: '{self.filename}'\n") + + # General info + lines.append(f"DXF version: {self.stats.get('dxfVersion', 'Unknown')}") + lines.append(f"File encoding: {self.stats.get('dxfEncoding', 'Unknown')}") + + # Scaling info + file_units = self.stats.get('fileUnits', 'Not specified') + source = self.stats.get('scalingSource', '') + if source: + lines.append(f"File units: {file_units} (from {source})") + else: + lines.append(f"File units: {file_units}") + + manual_scaling = self.stats.get('importSettings', {}).get('Manual scaling factor', '1.0') + lines.append(f"Manual scaling factor: {manual_scaling}") + + final_scaling = self.stats.get('finalScalingFactor', 1.0) + lines.append(f"Final scaling: 1 DXF unit = {final_scaling:.4f} mm") + lines.append("") + + # Timing + lines.append("Performance:") + cpp_time = self.stats.get('importTimeSeconds', 0.0) + lines.append(f" - C++ import time: {cpp_time:.4f} seconds") + lines.append(f" - Total import time: {self.total_time:.4f} seconds") + lines.append("") + + # Settings + lines.append("Import settings:") + settings = self.stats.get('importSettings', {}) + if settings: + for key, value in sorted(settings.items()): + lines.append(f" - {key}: {value}") + else: + lines.append(" (No settings recorded)") + lines.append("") + + # Counts + lines.append("Entity counts:") + total_read = 0 + unsupported_keys = self.stats.get('unsupportedFeatures', {}).keys() + unsupported_entity_names = set() + for key in unsupported_keys: + # Extract the entity name from the key string, e.g., 'HATCH' from "Entity type 'HATCH'" + entity_name_match = re.search(r"\'(.*?)\'", key) + if entity_name_match: + unsupported_entity_names.add(entity_name_match.group(1)) + + has_unsupported_indicator = False + entities = self.stats.get('entityCounts', {}) + if entities: + for key, value in sorted(entities.items()): + indicator = "" + if key in unsupported_entity_names: + indicator = " (*)" + has_unsupported_indicator = True + lines.append(f" - {key}: {value}{indicator}") + total_read += value + lines.append("----------------------------") + lines.append(f" Total entities read: {total_read}") + else: + lines.append(" (No entities recorded)") + lines.append(f"FreeCAD objects created: {self.stats.get('totalEntitiesCreated', 0)}") + lines.append("") + if has_unsupported_indicator: + lines.append("(*) Entity type not supported by importer.") + lines.append("") + + lines.append("Unsupported features:") + unsupported = self.stats.get('unsupportedFeatures', {}) + if unsupported: + for key, occurrences in sorted(unsupported.items()): + count = len(occurrences) + max_details_to_show = 5 + + details_list = [] + for i, (line, handle) in enumerate(occurrences): + if i >= max_details_to_show: + break + if handle: + details_list.append(f"line {line} (handle {handle})") + else: + details_list.append(f"line {line} (no handle available)") + + details_str = ", ".join(details_list) + if count > max_details_to_show: + lines.append(f" - {key}: {count} time(s). Examples: {details_str}, ...") + else: + lines.append(f" - {key}: {count} time(s) at {details_str}") + else: + lines.append(" (none)") + + lines.append("--- End of summary ---\n") + return "\n".join(lines) + + def report_to_console(self): + """ + Prints the formatted statistics string to the FreeCAD console. + """ + output_string = self.to_console_string() + FCC.PrintMessage(output_string) diff --git a/src/Mod/Import/App/AppImportPy.cpp b/src/Mod/Import/App/AppImportPy.cpp index b4349587ef..362700b83c 100644 --- a/src/Mod/Import/App/AppImportPy.cpp +++ b/src/Mod/Import/App/AppImportPy.cpp @@ -48,6 +48,7 @@ #endif #endif +#include #include "dxf/ImpExpDxf.h" #include "SketchExportHelper.h" #include @@ -413,8 +414,15 @@ private: ImpExpDxfRead dxf_file(EncodedName, pcDoc); dxf_file.setOptionSource(defaultOptions); dxf_file.setOptions(); + + auto startTime = std::chrono::high_resolution_clock::now(); dxf_file.DoRead(IgnoreErrors); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = endTime - startTime; + dxf_file.setImportTime(elapsed.count()); + pcDoc->recompute(); + return dxf_file.getStatsAsPyObject(); } catch (const Standard_Failure& e) { throw Py::RuntimeError(e.GetMessageString()); @@ -422,7 +430,6 @@ private: catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); } - return Py::None(); } diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.cpp b/src/Mod/Import/App/dxf/ImpExpDxf.cpp index 6b9279deaf..815624f038 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.cpp +++ b/src/Mod/Import/App/dxf/ImpExpDxf.cpp @@ -141,22 +141,32 @@ void ImpExpDxfRead::setOptions() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(getOptionSource().c_str()); + m_stats.importSettings.clear(); + m_preserveLayers = hGrp->GetBool("dxfUseDraftVisGroups", true); + m_stats.importSettings["Use layers"] = m_preserveLayers ? "Yes" : "No"; + 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"; } // 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 @@ -164,12 +174,25 @@ void ImpExpDxfRead::setOptions() // 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. - SetAdditionalScaling(hGrp->GetFloat("dxfScaling", 1.0)); + bool joinGeometry = hGrp->GetBool("joingeometry", false); + m_stats.importSettings["Join geometry"] = joinGeometry ? "Yes" : "No"; + + double scaling = hGrp->GetFloat("dxfScaling", 1.0); + SetAdditionalScaling(scaling); + m_stats.importSettings["Manual scaling factor"] = std::to_string(scaling); m_importAnnotations = hGrp->GetBool("dxftext", false); + m_stats.importSettings["Import texts and dimensions"] = m_importAnnotations ? "Yes" : "No"; + m_importPoints = hGrp->GetBool("dxfImportPoints", true); + m_stats.importSettings["Import points"] = m_importPoints ? "Yes" : "No"; + m_importPaperSpaceEntities = hGrp->GetBool("dxflayout", false); + m_stats.importSettings["Import layout objects"] = m_importPaperSpaceEntities ? "Yes" : "No"; + m_importHiddenBlocks = hGrp->GetBool("dxfstarblocks", false); + m_stats.importSettings["Import hidden blocks"] = m_importHiddenBlocks ? "Yes" : "No"; + // TODO: There is currently no option for this: m_importFrozenLayers = // hGrp->GetBool("dxffrozenLayers", false); // TODO: There is currently no option for this: m_importHiddenLayers = @@ -771,6 +794,7 @@ std::string ImpExpDxfRead::Deformat(const char* text) void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape, const char* nameBase) { + Reader.IncrementCreatedObjectCount(); auto pcFeature = Reader.document->addObject(nameBase); pcFeature->Shape.setValue(shape); Reader.MoveToLayer(pcFeature); @@ -778,6 +802,7 @@ void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape, } void ImpExpDxfRead::DrawingEntityCollector::AddObject(FeaturePythonBuilder shapeBuilder) { + Reader.IncrementCreatedObjectCount(); App::FeaturePython* shape = shapeBuilder(Reader.OCSOrientationTransform); if (shape != nullptr) { Reader.MoveToLayer(shape); @@ -1347,3 +1372,49 @@ void ImpExpDxfWrite::exportDiametricDim(Base::Vector3d textLocn, arc2[2] = arcPoint2.z; writeDiametricDim(text, arc1, arc2, dimText); } + +Py::Object ImpExpDxfRead::getStatsAsPyObject() +{ + // Create a Python dictionary to hold all import statistics. + Py::Dict statsDict; + + // Populate the dictionary with general information about the import. + statsDict.setItem("dxfVersion", Py::String(m_stats.dxfVersion)); + statsDict.setItem("dxfEncoding", Py::String(m_stats.dxfEncoding)); + statsDict.setItem("scalingSource", Py::String(m_stats.scalingSource)); + statsDict.setItem("fileUnits", Py::String(m_stats.fileUnits)); + statsDict.setItem("finalScalingFactor", Py::Float(m_stats.finalScalingFactor)); + statsDict.setItem("importTimeSeconds", Py::Float(m_stats.importTimeSeconds)); + statsDict.setItem("totalEntitiesCreated", Py::Long(m_stats.totalEntitiesCreated)); + + // Create a nested dictionary for the counts of each DXF entity type read. + Py::Dict entityCountsDict; + for (const auto& pair : m_stats.entityCounts) { + entityCountsDict.setItem(pair.first.c_str(), Py::Long(pair.second)); + } + statsDict.setItem("entityCounts", entityCountsDict); + + // Create a nested dictionary for the import settings used for this session. + Py::Dict importSettingsDict; + for (const auto& pair : m_stats.importSettings) { + importSettingsDict.setItem(pair.first.c_str(), Py::String(pair.second)); + } + statsDict.setItem("importSettings", importSettingsDict); + + // Create a nested dictionary for any unsupported DXF features encountered. + Py::Dict unsupportedFeaturesDict; + for (const auto& pair : m_stats.unsupportedFeatures) { + Py::List occurrencesList; + for (const auto& occurrence : pair.second) { + Py::Tuple infoTuple(2); + infoTuple.setItem(0, Py::Long(occurrence.first)); + infoTuple.setItem(1, Py::String(occurrence.second)); + occurrencesList.append(infoTuple); + } + unsupportedFeaturesDict.setItem(pair.first.c_str(), occurrencesList); + } + statsDict.setItem("unsupportedFeatures", unsupportedFeaturesDict); + + // Return the fully populated statistics dictionary to the Python caller. + return statsDict; +} diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.h b/src/Mod/Import/App/dxf/ImpExpDxf.h index c486e6eb7d..50b8afa558 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.h +++ b/src/Mod/Import/App/dxf/ImpExpDxf.h @@ -50,6 +50,8 @@ public: Py_XDECREF(DraftModule); } + Py::Object getStatsAsPyObject(); + bool ReadEntitiesSection() override; // CDxfRead's virtual functions @@ -204,6 +206,11 @@ private: std::string m_optionSource; protected: + friend class DrawingEntityCollector; + void IncrementCreatedObjectCount() + { + m_stats.totalEntitiesCreated++; + } virtual void ApplyGuiStyles(Part::Feature* /*object*/) const {} virtual void ApplyGuiStyles(App::FeaturePython* /*object*/) const diff --git a/src/Mod/Import/App/dxf/dxf.cpp b/src/Mod/Import/App/dxf/dxf.cpp index 257fb5a165..3e780d3e5b 100644 --- a/src/Mod/Import/App/dxf/dxf.cpp +++ b/src/Mod/Import/App/dxf/dxf.cpp @@ -26,6 +26,61 @@ using namespace std; + +namespace +{ + +std::string DxfUnitToString(DxfUnits::eDxfUnits_t unit) +{ + switch (unit) { + case DxfUnits::eInches: + return "Inches"; + case DxfUnits::eFeet: + return "Feet"; + case DxfUnits::eMiles: + return "Miles"; + case DxfUnits::eMillimeters: + return "Millimeters"; + case DxfUnits::eCentimeters: + return "Centimeters"; + case DxfUnits::eMeters: + return "Meters"; + case DxfUnits::eKilometers: + return "Kilometers"; + case DxfUnits::eMicroinches: + return "Microinches"; + case DxfUnits::eMils: + return "Mils"; + case DxfUnits::eYards: + return "Yards"; + case DxfUnits::eAngstroms: + return "Angstroms"; + case DxfUnits::eNanometers: + return "Nanometers"; + case DxfUnits::eMicrons: + return "Microns"; + case DxfUnits::eDecimeters: + return "Decimeters"; + case DxfUnits::eDekameters: + return "Dekameters"; + case DxfUnits::eHectometers: + return "Hectometers"; + case DxfUnits::eGigameters: + return "Gigameters"; + case DxfUnits::eAstronomicalUnits: + return "Astronomical Units"; + case DxfUnits::eLightYears: + return "Light Years"; + case DxfUnits::eParsecs: + return "Parsecs"; + case DxfUnits::eUnspecified: + default: + return "Unspecified"; + } +} + +} // namespace + static Base::Vector3d MakeVector3d(const double coordinates[3]) { // NOLINTNEXTLINE(readability/nolint) @@ -1947,6 +2002,9 @@ void CDxfRead::ProcessAllEntityAttributes() void CDxfRead::ResolveEntityAttributes() { m_entityAttributes.ResolveBylayerAttributes(*this); + if (m_entityAttributes.m_paperSpace) { + m_stats.entityCounts["ENTITIES_IN_PAPERSPACE"]++; + } // TODO: Look at the space and layer (hidden/frozen?) and options and return false if the entity // is not needed. // TODO: INSERT must not call this because an INSERT on a hidden layer should always be @@ -2284,8 +2342,8 @@ bool CDxfRead::ReadDimension() bool CDxfRead::ReadUnknownEntity() { - UnsupportedFeature("Entity type '%s'", m_record_data); ProcessAllEntityAttributes(); + UnsupportedFeature("Entity type '%s'", m_current_entity_name.c_str()); return true; } @@ -2294,6 +2352,7 @@ bool CDxfRead::ReadBlockInfo() int blockType = 0; std::string blockName; InitializeAttributes(); + m_stats.entityCounts["BLOCK"]++; // Both 2 and 3 are the block name. SetupStringAttribute(eName, blockName); SetupStringAttribute(eExtraText, blockName); @@ -2344,11 +2403,8 @@ void CDxfRead::UnsupportedFeature(const char* format, args&&... argValuess) { // NOLINTNEXTLINE(runtime/printf) std::string formattedMessage = fmt::sprintf(format, std::forward(argValuess)...); - // We place these formatted messages in a map, count their occurrences and not their first - // occurrence. - if (m_unsupportedFeaturesNoted[formattedMessage].first++ == 0) { - m_unsupportedFeaturesNoted[formattedMessage].second = m_line; - } + m_stats.unsupportedFeatures[formattedMessage].emplace_back(m_current_entity_line_number, + m_current_entity_handle); } bool CDxfRead::get_next_record() @@ -2538,6 +2594,8 @@ bool CDxfRead::ReadVersion() m_version = RUnknown; } + m_stats.dxfVersion = m_record_data; + return ResolveEncoding(); } @@ -2606,6 +2664,9 @@ bool CDxfRead::ResolveEncoding() Py_DECREF(pyDecoder); Py_DECREF(pyUTF8Decoder); } + + m_stats.dxfEncoding = m_encoding; + return !m_encoding.empty(); } @@ -2664,17 +2725,6 @@ void CDxfRead::DoRead(const bool ignore_errors /* = false */) } } FinishImport(); - - // Flush out any unsupported features messages - if (!m_unsupportedFeaturesNoted.empty()) { - ImportError("Unsupported DXF features:\n"); - for (auto& featureInfo : m_unsupportedFeaturesNoted) { - ImportError("%s: %d time(s) first at line %d\n", - featureInfo.first, - featureInfo.second.first, - featureInfo.second.second); - } - } } catch (const Base::Exception& e) { // This catches specific FreeCAD exceptions and re-throws them. @@ -2747,8 +2797,12 @@ void CDxfRead::ProcessLayerReference(CDxfRead* object, void* target) } bool CDxfRead::ReadEntity() { + m_current_entity_line_number = m_line; + m_current_entity_name = m_record_data; InitializeAttributes(); m_entityAttributes.SetDefaults(); + m_current_entity_handle.clear(); + SetupStringAttribute(eHandle, m_current_entity_handle); EntityNormalVector.Set(0, 0, 1); Setup3DVectorAttribute(eExtrusionDirection, EntityNormalVector); SetupStringAttribute(eLinetypeName, m_entityAttributes.m_LineType); @@ -2759,6 +2813,9 @@ bool CDxfRead::ReadEntity() m_entityAttributes.m_paperSpace); // TODO: Ensure the stream is noboolalpha (for that // matter ensure the stream has the "C" locale SetupValueAttribute(eColor, m_entityAttributes.m_Color); + + m_stats.entityCounts[m_record_data]++; + // The entity record is already the current record and is already checked as a type 0 record if (IsObjectName("LINE")) { return ReadLine(); @@ -2809,13 +2866,11 @@ bool CDxfRead::ReadHeaderSection() if (m_record_type == eObjectType && IsObjectName("ENDSEC")) { if (m_unitScalingFactor == 0.0) { // Neither INSUNITS nor MEASUREMENT found, assume 1 DXF unit = 1mm - // TODO: Perhaps this default should depend on the current measuring units of the - // app. + // TODO: Perhaps this default should depend on the current project's unit system m_unitScalingFactor = m_additionalScaling; - ImportObservation("No INSUNITS or MEASUREMENT; setting scaling to 1 DXF unit = " - "%gmm based on DXF scaling option\n", - m_unitScalingFactor); + m_stats.fileUnits = "Unspecified (Defaulting to 1:1)"; } + m_stats.finalScalingFactor = m_unitScalingFactor; return true; } if (m_record_type != eVariableName) { @@ -2843,14 +2898,14 @@ bool CDxfRead::ReadVariable() if (!ParseValue(this, &varValue)) { ImportError("Failed to get integer from INSUNITS value '%s'\n", m_record_data); } - else if (auto units = DxfUnits::eDxfUnits_t(varValue); !DxfUnits::IsValid(units)) { - ImportError("Unknown value '%d' for INSUNITS\n", varValue); - } else { + auto units = DxfUnits::eDxfUnits_t(varValue); + if (!DxfUnits::IsValid(units)) { + units = DxfUnits::eUnspecified; + } m_unitScalingFactor = DxfUnits::Factor(units) * m_additionalScaling; - ImportObservation("Setting scaling to 1 DXF unit = %gmm based on INSUNITS and " - "DXF scaling option\n", - m_unitScalingFactor); + m_stats.scalingSource = "$INSUNITS"; + m_stats.fileUnits = DxfUnitToString(units); } return true; } @@ -2858,12 +2913,10 @@ bool CDxfRead::ReadVariable() get_next_record(); int varValue = 1; if (m_unitScalingFactor == 0.0 && ParseValue(this, &varValue)) { - m_unitScalingFactor = - DxfUnits::Factor(varValue != 0 ? DxfUnits::eMillimeters : DxfUnits::eInches) - * m_additionalScaling; - ImportObservation("Setting scaling to 1 DXF unit = %gmm based on MEASUREMENT and " - "DXF scaling option\n", - m_unitScalingFactor); + auto units = (varValue != 0 ? DxfUnits::eMillimeters : DxfUnits::eInches); + m_unitScalingFactor = DxfUnits::Factor(units) * m_additionalScaling; + m_stats.scalingSource = "$MEASUREMENT"; + m_stats.fileUnits = DxfUnitToString(units); } return true; } @@ -3131,3 +3184,5 @@ Base::Color CDxfRead::ObjectColor(ColorIndex_t index) return result; } // NOLINTEND(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + +template void CDxfRead::UnsupportedFeature<>(const char*); diff --git a/src/Mod/Import/App/dxf/dxf.h b/src/Mod/Import/App/dxf/dxf.h index c3da0d8fba..92b3ce472c 100644 --- a/src/Mod/Import/App/dxf/dxf.h +++ b/src/Mod/Import/App/dxf/dxf.h @@ -176,6 +176,21 @@ struct LWPolyDataOut point3D Extr; }; +// Statistics reporting structure +struct DxfImportStats +{ + double importTimeSeconds = 0.0; + std::string dxfVersion; + std::string dxfEncoding; + std::string scalingSource; + std::string fileUnits; + double finalScalingFactor = 1.0; + std::map entityCounts; + std::map importSettings; + std::map>> unsupportedFeatures; + int totalEntitiesCreated = 0; +}; + // "using" for enums is not supported by all platforms // https://stackoverflow.com/questions/41167119/how-to-fix-a-wsubobject-linkage-warning @@ -185,6 +200,7 @@ enum eDXFGroupCode_t ePrimaryText = 1, eName = 2, eExtraText = 3, + eHandle = 5, eLinetypeName = 6, eTextStyleName = 7, eLayerName = 8, @@ -447,6 +463,9 @@ private: bool m_not_eof = true; int m_line = 0; bool m_repeat_last_record = false; + int m_current_entity_line_number = 0; + std::string m_current_entity_name; + std::string m_current_entity_handle; // The scaling from DXF units to millimetres. // This does not include the dxfScaling option @@ -455,6 +474,7 @@ private: double m_unitScalingFactor = 0.0; protected: + DxfImportStats m_stats; // An additional scaling factor which can be modified before readDXF is called, and will be // incorporated into m_unitScalingFactor. void SetAdditionalScaling(double scaling) @@ -700,7 +720,6 @@ protected: void UnsupportedFeature(const char* format, args&&... argValues); private: - std::map> m_unsupportedFeaturesNoted; std::string m_CodePage; // Code Page name from $DWGCODEPAGE or null if none/not read yet // The following was going to be python's canonical name for the encoding, but this is (a) not // easily found and (b) does not speed up finding the encoding object. @@ -846,6 +865,10 @@ public: { return m_fail; } + void setImportTime(double seconds) + { + m_stats.importTimeSeconds = seconds; + } void DoRead(bool ignore_errors = false); // this reads the file and calls the following functions virtual void StartImport() diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index 35319202c6..0e4dc03c11 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -46,6 +46,7 @@ #endif #endif +#include #include "ExportOCAFGui.h" #include "ImportOCAFGui.h" #include "OCAFBrowser.h" @@ -401,8 +402,15 @@ private: ImpExpDxfReadGui dxf_file(EncodedName, pcDoc); dxf_file.setOptionSource(defaultOptions); dxf_file.setOptions(); + + auto startTime = std::chrono::high_resolution_clock::now(); dxf_file.DoRead(IgnoreErrors); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = endTime - startTime; + dxf_file.setImportTime(elapsed.count()); + pcDoc->recompute(); + return dxf_file.getStatsAsPyObject(); } catch (const Standard_Failure& e) { throw Py::RuntimeError(e.GetMessageString()); @@ -410,7 +418,6 @@ private: catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); } - return Py::None(); } Py::Object exportOptions(const Py::Tuple& args)