Merge pull request #21985 from furgo16/dxf-stats-reporter
Import: add DXF statistics reporter
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include "dxf/ImpExpDxf.h"
|
||||
#include "SketchExportHelper.h"
|
||||
#include <App/Application.h>
|
||||
@@ -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<double> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<Part::Feature>(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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<args>(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<int>(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<int>(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*);
|
||||
|
||||
@@ -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<std::string, int> entityCounts;
|
||||
std::map<std::string, std::string> importSettings;
|
||||
std::map<std::string, std::vector<std::pair<int, std::string>>> 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<std::string, std::pair<int, int>> 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()
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#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<double> 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)
|
||||
|
||||
Reference in New Issue
Block a user