From abd411f934e56469e4748b5ecf688eeba0108f2d Mon Sep 17 00:00:00 2001 From: Kevin Martin Date: Fri, 24 Nov 2023 11:44:17 -0500 Subject: [PATCH] Add color on DXF import, refactor code Colors as assigned to imported drawing entities if they are not merged with other entities. The code has been refactored to remove much duplication in reading of attributes. The code gives brief messages on the Python console about unsupported DXF festures and also issues some new errors. There is no support yet for making colors 1-9 and 250-255 contrast with the creeen background color. Colors are generated by code rather than a lookup table; this code can eventually modify the colors it generates to contrast with a specific background color. --- src/Mod/Import/App/dxf/ImpExpDxf.cpp | 77 +- src/Mod/Import/App/dxf/ImpExpDxf.h | 7 +- src/Mod/Import/App/dxf/dxf.cpp | 2947 ++++++++--------------- src/Mod/Import/App/dxf/dxf.h | 150 +- src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp | 56 +- src/Mod/Import/Gui/dxf/ImpExpDxfGui.h | 6 + 6 files changed, 1193 insertions(+), 2050 deletions(-) diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.cpp b/src/Mod/Import/App/dxf/ImpExpDxf.cpp index 4d639774a7..1fd4c144fa 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.cpp +++ b/src/Mod/Import/App/dxf/ImpExpDxf.cpp @@ -55,12 +55,15 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include "ImpExpDxf.h" @@ -92,17 +95,12 @@ void ImpExpDxfRead::setOptions() optionScaling = hGrp->GetFloat("dxfScaling", 1.0); } -gp_Pnt ImpExpDxfRead::makePoint(const double* p) +gp_Pnt ImpExpDxfRead::makePoint(const double point3d[3]) const { - double sp1(p[0]); - double sp2(p[1]); - double sp3(p[2]); if (optionScaling != 1.0) { - sp1 = sp1 * optionScaling; - sp2 = sp2 * optionScaling; - sp3 = sp3 * optionScaling; + return {point3d[0] * optionScaling, point3d[1] * optionScaling, point3d[2] * optionScaling}; } - return {sp1, sp2, sp3}; + return {point3d[0], point3d[1], point3d[2]}; } void ImpExpDxfRead::OnReadLine(const double* s, const double* e, bool /*hidden*/) @@ -333,13 +331,27 @@ void ImpExpDxfRead::OnReadText(const double* point, PyObject* placement = new Base::PlacementPy(Base::Placement(insertionPoint, rot)); draftModule = PyImport_ImportModule("Draft"); if (draftModule != nullptr) { - PyObject_CallMethod(draftModule, "make_text", "sOif", text, placement, 0, height); + // returns a wrapped App::FeaturePython + auto builtText = static_cast*>( + PyObject_CallMethod(draftModule, + "make_text", + "sOif", + text, + placement, + 0, + height)); + if (builtText != nullptr) { + ApplyGuiStyles( + static_cast(builtText->getDocumentObjectPtr())); + } } // We own all the return values so we must release them. Py_DECREF(placement); Py_XDECREF(draftModule); } - // else std::cout << "skipped text in block: " << LayerName() << std::endl; + else { + UnsupportedFeature("TEXT or MTEXT in BLOCK definition"); + } } } @@ -395,27 +407,36 @@ void ImpExpDxfRead::OnReadDimension(const double* s, double /*rotation*/) { if (optionImportAnnotations) { - Base::Interpreter().runString("import Draft"); - Base::Interpreter().runStringArg("p1=FreeCAD.Vector(%f,%f,%f)", - s[0] * optionScaling, - s[1] * optionScaling, - s[2] * optionScaling); - Base::Interpreter().runStringArg("p2=FreeCAD.Vector(%f,%f,%f)", - e[0] * optionScaling, - e[1] * optionScaling, - e[2] * optionScaling); - Base::Interpreter().runStringArg("p3=FreeCAD.Vector(%f,%f,%f)", - point[0] * optionScaling, - point[1] * optionScaling, - point[2] * optionScaling); - Base::Interpreter().runString("Draft.makeDimension(p1,p2,p3)"); + PyObject* draftModule = nullptr; + PyObject* start = new Base::VectorPy(Base::Vector3d(s[0], s[1], s[2]) * optionScaling); + PyObject* end = new Base::VectorPy(Base::Vector3d(e[0], e[1], e[2]) * optionScaling); + PyObject* lineLocation = + new Base::VectorPy(Base::Vector3d(point[0], point[1], point[2]) * optionScaling); + draftModule = PyImport_ImportModule("Draft"); + if (draftModule != nullptr) { + // returns a wrapped App::FeaturePython + auto builtDim = static_cast*>( + PyObject_CallMethod(draftModule, + "make_linear_dimension", + "OOO", + start, + end, + lineLocation)); + if (builtDim != nullptr) { + ApplyGuiStyles(static_cast(builtDim->getDocumentObjectPtr())); + } + } + // We own all the return values so we must release them. + Py_DECREF(start); + Py_DECREF(end); + Py_DECREF(lineLocation); + Py_XDECREF(draftModule); } } void ImpExpDxfRead::AddObject(Part::TopoShape* shape) { - // std::cout << "layer:" << LayerName() << std::endl; std::vector vec; if (layers.count(LayerName())) { vec = layers[LayerName()]; @@ -427,6 +448,7 @@ void ImpExpDxfRead::AddObject(Part::TopoShape* shape) Part::Feature* pcFeature = static_cast(document->addObject("Part::Feature", "Shape")); pcFeature->Shape.setValue(shape->getShape()); + ApplyGuiStyles(pcFeature); } } } @@ -517,10 +539,7 @@ void gPntToTuple(double* result, gp_Pnt& p) point3D gPntTopoint3D(gp_Pnt& p) { - point3D result; - result.x = p.X(); - result.y = p.Y(); - result.z = p.Z(); + point3D result = {p.X(), p.Y(), p.Z()}; return result; } diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.h b/src/Mod/Import/App/dxf/ImpExpDxf.h index b6a38970d0..fff54e0ef6 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.h +++ b/src/Mod/Import/App/dxf/ImpExpDxf.h @@ -27,6 +27,7 @@ #include #include +#include #include "dxf.h" @@ -83,9 +84,13 @@ public: void setOptions(); private: - gp_Pnt makePoint(const double* p); + gp_Pnt makePoint(const double point3d[3]) const; protected: + virtual void ApplyGuiStyles(Part::Feature*) + {} + virtual void ApplyGuiStyles(App::FeaturePython*) + {} App::Document* document; bool optionGroupLayers; bool optionImportAnnotations; diff --git a/src/Mod/Import/App/dxf/dxf.cpp b/src/Mod/Import/App/dxf/dxf.cpp index f681916027..b91f17fe3c 100644 --- a/src/Mod/Import/App/dxf/dxf.cpp +++ b/src/Mod/Import/App/dxf/dxf.cpp @@ -15,6 +15,7 @@ #include "dxf.h" #include +#include #include #include #include @@ -777,11 +778,7 @@ void CDxfWrite::writeEllipse(const double* c, double end_angle, bool endIsCW) { - double m[3]; - m[2] = 0; - m[0] = major_radius * sin(rotation); - m[1] = major_radius * cos(rotation); - + double m[3] = {major_radius * sin(rotation), major_radius * cos(rotation), 0}; double ratio = minor_radius / major_radius; if (!endIsCW) { // end is NOT CW from start @@ -894,13 +891,13 @@ void CDxfWrite::writeSpline(const SplineDataOut& sd) (*m_ssEntity) << w << endl; } - for (auto& c : sd.control) { + for (auto& center : sd.control) { (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << c.x << endl; // X in WCS coordinates + (*m_ssEntity) << center.x << endl; // X in WCS coordinates (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << c.y << endl; // Y in WCS coordinates + (*m_ssEntity) << center.y << endl; // Y in WCS coordinates (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << c.z << endl; // Z in WCS coordinates + (*m_ssEntity) << center.z << endl; // Z in WCS coordinates } for (auto& f : sd.fit) { (*m_ssEntity) << " 11" << endl; @@ -1688,9 +1685,9 @@ void CDxfWrite::writeRadialDimBlock(const double* centerPoint, getBlockHandle(), m_saveBlkRecordHandle); - Base::Vector3d c(centerPoint[0], centerPoint[1], centerPoint[2]); + Base::Vector3d center(centerPoint[0], centerPoint[1], centerPoint[2]); Base::Vector3d a(arcPoint[0], arcPoint[1], arcPoint[2]); - Base::Vector3d para = a - c; + Base::Vector3d para = a - center; double arrowLen = 5.0; // magic number double arrowWidth = arrowLen / 6.0 / 2.0; // magic number calc! para.Normalize(); @@ -1798,30 +1795,13 @@ void CDxfWrite::writeObjectsSection() CDxfRead::CDxfRead(const char* filepath) { // start the file - memset(m_str, '\0', sizeof(m_str)); - memset(m_unused_line, '\0', sizeof(m_unused_line)); - m_fail = false; - m_ColorIndex = 0; - m_eUnits = eMillimeters; - m_measurement_inch = false; - strcpy(m_layer_name, "0"); // Default layer name - memset(m_section_name, '\0', sizeof(m_section_name)); - memset(m_block_name, '\0', sizeof(m_block_name)); - m_ignore_errors = true; - - m_version = RUnknown; - m_CodePage = nullptr; - m_encoding = nullptr; - - m_ifs = new Base::ifstream(Base::FileInfo(filepath)); + m_ifs = new ifstream(filepath); if (!(*m_ifs)) { m_fail = true; - printf("DXF file didn't load\n"); + ImportError("DXF file didn't load\n"); return; } m_ifs->imbue(std::locale("C")); - - stringToUTF8 = &CDxfRead::UTF8ToUTF8; } CDxfRead::~CDxfRead() @@ -1890,1449 +1870,646 @@ double CDxfRead::mm(double value) const } // End switch } // End mm() method +// +// Setup for ProcessCommonEntityAttribute +void CDxfRead::Setup3DVectorAttribute(int x_record_type, double destination[3]) +{ + SetupScaledDoubleAttribute(x_record_type, destination[0]); + SetupScaledDoubleAttribute(x_record_type + 10, destination[1]); + SetupScaledDoubleAttribute(x_record_type + 20, destination[2]); + destination[0] = destination[1] = destination[2] = 0.0; +} +void CDxfRead::Setup3DCoordinatesIntoLists(int x_record_type, + list& x_destination, + list& y_destination, + list& z_destination) +{ + SetupScaledDoubleIntoList(x_record_type, x_destination); + SetupScaledDoubleIntoList(x_record_type + 10, y_destination); + SetupScaledDoubleIntoList(x_record_type + 20, z_destination); +} +void CDxfRead::SetupScaledDoubleAttribute(int x_record_type, double& destination) +{ + m_coordinate_attributes.emplace(x_record_type, std::pair(&ProcessScaledDouble, &destination)); +} +void CDxfRead::SetupScaledDoubleIntoList(int x_record_type, list& destination) +{ + m_coordinate_attributes.emplace(x_record_type, + std::pair(&ProcessScaledDoubleIntoList, &destination)); +} +void CDxfRead::SetupStringAttribute(int x_record_type, char* destination) +{ + m_coordinate_attributes.emplace(x_record_type, std::pair(&ProcessString, destination)); +} +void CDxfRead::SetupStringAttribute(int x_record_type, std::string& destination) +{ + m_coordinate_attributes.emplace(x_record_type, std::pair(&ProcessStdString, &destination)); +} +template +void CDxfRead::SetupValueAttribute(int record_type, T& destination) +{ + m_coordinate_attributes.emplace(record_type, std::pair(&ProcessValue, &destination)); +} +// +// Static processing helpers for ProcessCommonEntityAttribute +void CDxfRead::ProcessScaledDouble(CDxfRead* object, void* target) +{ + std::istringstream ss; + ss.imbue(std::locale("C")); + + ss.str(object->m_record_data); + double value; + ss >> value; + if (ss.fail()) { + object->ImportError("Unable to parse value '%s', using zero as its value\n", + object->m_record_data); + value = 0; + } + *static_cast(target) = object->mm(value); +} +void CDxfRead::ProcessScaledDoubleIntoList(CDxfRead* object, void* target) +{ + std::istringstream ss; + ss.imbue(std::locale("C")); + + ss.str(object->m_record_data); + double value; + ss >> value; + if (ss.fail()) { + object->ImportError("Unable to parse value '%s', using zero as its value\n", + object->m_record_data); + value = 0; + } + static_cast*>(target)->push_back(object->mm(value)); +} +template +void CDxfRead::ProcessValue(CDxfRead* object, void* target) +{ + std::istringstream ss; + ss.imbue(std::locale("C")); + + ss.str(object->m_record_data); + ss >> *static_cast(target); + if (ss.fail()) { + object->ImportError("Unable to parse value '%s', using zero as its value\n", + object->m_record_data); + *static_cast(target) = 0; + } +} +void CDxfRead::ProcessStdString(CDxfRead* object, void* target) +{ + *static_cast(target) = object->m_record_data; +} +void CDxfRead::ProcessString(CDxfRead* object, void* target) +{ + strcpy(static_cast(target), object->m_record_data); +} + +void CDxfRead::InitializeAttributes() +{ + m_coordinate_attributes.clear(); +} +void CDxfRead::InitializeCommonEntityAttributes() +{ + InitializeAttributes(); + strcpy(m_layer_name, "0"); + m_LineType[0] = '\0'; + m_ColorIndex = ColorBylayer; + SetupStringAttribute(6, m_LineType); + SetupStringAttribute(8, m_layer_name); + SetupValueAttribute(62, m_ColorIndex); +} +bool CDxfRead::ProcessAttribute() +{ + auto found = m_coordinate_attributes.find(m_record_type); + if (found != m_coordinate_attributes.end()) { + (*found->second.first)(this, found->second.second); + return true; + } + return false; +} +void CDxfRead::ProcessAllAttributes() +{ + while (get_next_record() && m_record_type != 0) { + ProcessAttribute(); + } + repeat_last_record(); +} + +// +// The individual Entity reader functions +// These return false if they catch an exception and ignore it because of ignore_errors. bool CDxfRead::ReadLine() { - double s[3] = {0, 0, 0}; - double e[3] = {0, 0, 0}; - bool hidden = false; + double start[3], end[3]; + Setup3DVectorAttribute(10, start); + Setup3DVectorAttribute(11, end); + ProcessAllAttributes(); - while (!((*m_ifs).eof())) { - get_line(); - int n; - - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadLine() Failed to read integer from '%s'\n", m_str); - return false; - } - - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with line - ResolveColorIndex(); - OnReadLine(s, e, hidden); - hidden = false; - return true; - - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 6: // line style name follows - get_line(); - if (m_str[0] == 'h' || m_str[0] == 'H') { - hidden = true; - } - break; - - case 10: - // start x - get_line(); - ss.str(m_str); - ss >> s[0]; - s[0] = mm(s[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // start y - get_line(); - ss.str(m_str); - ss >> s[1]; - s[1] = mm(s[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // start z - get_line(); - ss.str(m_str); - ss >> s[2]; - s[2] = mm(s[2]); - if (ss.fail()) { - return false; - } - break; - case 11: - // end x - get_line(); - ss.str(m_str); - ss >> e[0]; - e[0] = mm(e[0]); - if (ss.fail()) { - return false; - } - break; - case 21: - // end y - get_line(); - ss.str(m_str); - ss >> e[1]; - e[1] = mm(e[1]); - if (ss.fail()) { - return false; - } - break; - case 31: - // end z - get_line(); - ss.str(m_str); - ss >> e[2]; - e[2] = mm(e[2]); - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - - try { - ResolveColorIndex(); - OnReadLine(s, e, false); - } - catch (...) { - if (!IgnoreErrors()) { - throw; // Re-throw the exception. - } - } - - return false; + OnReadLine(start, end, LineTypeIsHidden()); + return true; } bool CDxfRead::ReadPoint() { - double s[3] = {0, 0, 0}; + double location[3]; + Setup3DVectorAttribute(10, location); + ProcessAllAttributes(); - while (!((*m_ifs).eof())) { - get_line(); - int n; - - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadPoint() Failed to read integer from '%s'\n", m_str); - return false; - } - - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with line - ResolveColorIndex(); - OnReadPoint(s); - return true; - - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 10: - // start x - get_line(); - ss.str(m_str); - ss >> s[0]; - s[0] = mm(s[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // start y - get_line(); - ss.str(m_str); - ss >> s[1]; - s[1] = mm(s[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // start z - get_line(); - ss.str(m_str); - ss >> s[2]; - s[2] = mm(s[2]); - if (ss.fail()) { - return false; - } - break; - - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - - try { - ResolveColorIndex(); - OnReadPoint(s); - } - catch (...) { - if (!IgnoreErrors()) { - throw; // Re-throw the exception. - } - } - - return false; + OnReadPoint(location); + return true; } bool CDxfRead::ReadArc() { - double start_angle = 0.0; // in degrees - double end_angle = 0.0; - double radius = 0.0; - double c[3] = {0, 0, 0}; // centre + double start_angle_degrees = 0; + double end_angle_degrees = 0; + double radius = 0; + double centre[3]; double z_extrusion_dir = 1.0; - bool hidden = false; - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadArc() Failed to read integer from '%s'\n", m_str); - return false; - } + Setup3DVectorAttribute(10, centre); + SetupScaledDoubleAttribute(40, radius); + SetupValueAttribute(50, start_angle_degrees); + SetupValueAttribute(51, end_angle_degrees); + SetupValueAttribute(230, z_extrusion_dir); + ProcessAllAttributes(); - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with arc - ResolveColorIndex(); - OnReadArc(start_angle, end_angle, radius, c, z_extrusion_dir, hidden); - hidden = false; - return true; - - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 6: // line style name follows - get_line(); - if (m_str[0] == 'h' || m_str[0] == 'H') { - hidden = true; - } - break; - - case 10: - // centre x - get_line(); - ss.str(m_str); - ss >> c[0]; - c[0] = mm(c[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // centre y - get_line(); - ss.str(m_str); - ss >> c[1]; - c[1] = mm(c[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // centre z - get_line(); - ss.str(m_str); - ss >> c[2]; - c[2] = mm(c[2]); - if (ss.fail()) { - return false; - } - break; - case 40: - // radius - get_line(); - ss.str(m_str); - ss >> radius; - radius = mm(radius); - if (ss.fail()) { - return false; - } - break; - case 50: - // start angle - get_line(); - ss.str(m_str); - ss >> start_angle; - if (ss.fail()) { - return false; - } - break; - case 51: - // end angle - get_line(); - ss.str(m_str); - ss >> end_angle; - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - - case 100: - case 39: - case 210: - case 220: - // skip the next line - get_line(); - break; - case 230: - // Z extrusion direction for arc - get_line(); - ss.str(m_str); - ss >> z_extrusion_dir; - if (ss.fail()) { - return false; - } - break; - - default: - // skip the next line - get_line(); - break; - } - } - ResolveColorIndex(); - OnReadArc(start_angle, end_angle, radius, c, z_extrusion_dir, false); - return false; + OnReadArc(start_angle_degrees, + end_angle_degrees, + radius, + centre, + z_extrusion_dir, + LineTypeIsHidden()); + return true; } bool CDxfRead::ReadSpline() { struct SplineData sd; - sd.norm[0] = 0; - sd.norm[1] = 0; - sd.norm[2] = 1; sd.degree = 0; sd.knots = 0; sd.flag = 0; sd.control_points = 0; sd.fit_points = 0; - double temp_double; + Setup3DVectorAttribute(210, sd.norm); + SetupValueAttribute(70, sd.flag); + SetupValueAttribute(71, sd.degree); + SetupValueAttribute(72, sd.knots); + SetupValueAttribute(73, sd.control_points); + SetupValueAttribute(74, sd.fit_points); + SetupScaledDoubleIntoList(40, sd.knot); + SetupScaledDoubleIntoList(41, sd.weight); + Setup3DCoordinatesIntoLists(10, sd.controlx, sd.controly, sd.controlz); + Setup3DCoordinatesIntoLists(11, sd.fitx, sd.fity, sd.fitz); + Setup3DCoordinatesIntoLists(12, sd.starttanx, sd.starttany, sd.starttanz); + Setup3DCoordinatesIntoLists(12, sd.endtanx, sd.endtany, sd.endtanz); + ProcessAllAttributes(); - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadSpline() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with Spline - ResolveColorIndex(); - OnReadSpline(sd); - return true; - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - case 210: - // normal x - get_line(); - ss.str(m_str); - ss >> sd.norm[0]; - if (ss.fail()) { - return false; - } - break; - case 220: - // normal y - get_line(); - ss.str(m_str); - ss >> sd.norm[1]; - if (ss.fail()) { - return false; - } - break; - case 230: - // normal z - get_line(); - ss.str(m_str); - ss >> sd.norm[2]; - if (ss.fail()) { - return false; - } - break; - case 70: - // flag - get_line(); - ss.str(m_str); - ss >> sd.flag; - if (ss.fail()) { - return false; - } - break; - case 71: - // degree - get_line(); - ss.str(m_str); - ss >> sd.degree; - if (ss.fail()) { - return false; - } - break; - case 72: - // knots - get_line(); - ss.str(m_str); - ss >> sd.knots; - if (ss.fail()) { - return false; - } - break; - case 73: - // control points - get_line(); - ss.str(m_str); - ss >> sd.control_points; - if (ss.fail()) { - return false; - } - break; - case 74: - // fit points - get_line(); - ss.str(m_str); - ss >> sd.fit_points; - if (ss.fail()) { - return false; - } - break; - case 12: - // starttan x - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.starttanx.push_back(temp_double); - break; - case 22: - // starttan y - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.starttany.push_back(temp_double); - break; - case 32: - // starttan z - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.starttanz.push_back(temp_double); - break; - case 13: - // endtan x - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.endtanx.push_back(temp_double); - break; - case 23: - // endtan y - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.endtany.push_back(temp_double); - break; - case 33: - // endtan z - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.endtanz.push_back(temp_double); - break; - case 40: - // knot - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.knot.push_back(temp_double); - break; - case 41: - // weight - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.weight.push_back(temp_double); - break; - case 10: - // control x - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.controlx.push_back(temp_double); - break; - case 20: - // control y - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.controly.push_back(temp_double); - break; - case 30: - // control z - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.controlz.push_back(temp_double); - break; - case 11: - // fit x - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.fitx.push_back(temp_double); - break; - case 21: - // fit y - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.fity.push_back(temp_double); - break; - case 31: - // fit z - get_line(); - ss.str(m_str); - ss >> temp_double; - temp_double = mm(temp_double); - if (ss.fail()) { - return false; - } - sd.fitz.push_back(temp_double); - break; - case 42: - case 43: - case 44: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - ResolveColorIndex(); OnReadSpline(sd); - return false; + return true; } - bool CDxfRead::ReadCircle() { double radius = 0.0; - double c[3] = {0, 0, 0}; // centre - bool hidden = false; + double centre[3]; - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadCircle() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with Circle - ResolveColorIndex(); - OnReadCircle(c, radius, hidden); - hidden = false; - return true; + Setup3DVectorAttribute(10, centre); + SetupScaledDoubleAttribute(40, radius); + ProcessAllAttributes(); - case 6: // line style name follows - get_line(); - if (m_str[0] == 'h' || m_str[0] == 'H') { - hidden = true; - } - break; - - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 10: - // centre x - get_line(); - ss.str(m_str); - ss >> c[0]; - c[0] = mm(c[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // centre y - get_line(); - ss.str(m_str); - ss >> c[1]; - c[1] = mm(c[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // centre z - get_line(); - ss.str(m_str); - ss >> c[2]; - c[2] = mm(c[2]); - if (ss.fail()) { - return false; - } - break; - case 40: - // radius - get_line(); - ss.str(m_str); - ss >> radius; - radius = mm(radius); - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - ResolveColorIndex(); - OnReadCircle(c, radius, false); - return false; + OnReadCircle(centre, radius, LineTypeIsHidden()); + return true; } - bool CDxfRead::ReadText() { - double c[3]; // coordinate + double insertionPoint[3]; double height = 0.03082; - double rotation = 0.0; + double rotation = 0; std::string textPrefix; - memset(c, 0, sizeof(c)); - - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadText() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - ResolveColorIndex(); - { - const char* utfStr = (this->*stringToUTF8)(textPrefix.c_str()); - OnReadText(c, height * 25.4 / 72.0, utfStr, rotation); - if (utfStr == m_str) { - delete utfStr; - } - } - return true; - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 10: - // centre x - get_line(); - ss.str(m_str); - ss >> c[0]; - c[0] = mm(c[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // centre y - get_line(); - ss.str(m_str); - ss >> c[1]; - c[1] = mm(c[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // centre z - get_line(); - ss.str(m_str); - ss >> c[2]; - c[2] = mm(c[2]); - if (ss.fail()) { - return false; - } - break; - case 40: - // text height - get_line(); - ss.str(m_str); - ss >> height; - height = mm(height); - if (ss.fail()) { - return false; - } - break; - case 50: - // text rotation - get_line(); - ss.str(m_str); - ss >> rotation; - if (ss.fail()) { - return false; - } - break; - case 3: - // Additional text that goes before the type 1 text - // Note that if breaking the text into type-3 records splits a UFT-8 encoding we do - // the decoding after splicing the lines together. I'm not sure if this actually - // occurs, but handling the text this way will treat this condition properly. - get_line(); - textPrefix.append(m_str); - break; - case 1: - // final text - get_line(); - textPrefix.append(m_str); - break; - - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + Setup3DVectorAttribute(10, insertionPoint); + SetupScaledDoubleAttribute(40, height); + SetupValueAttribute(50, rotation); + while (get_next_record() && m_record_type != 0) { + if (!ProcessAttribute()) { + switch (m_record_type) { + case 3: + // Additional text that goes before the type 1 text + // Note that if breaking the text into type-3 records splits a UFT-8 encoding we + // do the decoding after splicing the lines together. I'm not sure if this + // actually occurs, but handling the text this way will treat this condition + // properly. + textPrefix.append(m_record_data); + break; + case 1: + // final text + textPrefix.append(m_record_data); + break; + } } } - return false; + const char* utfStr = (this->*stringToUTF8)(textPrefix.c_str()); + if (utfStr != nullptr) { + OnReadText(insertionPoint, height * 25.4 / 72.0, utfStr, rotation); + if (utfStr != textPrefix.c_str()) { + delete utfStr; + } + } + else { + ImportError("Unable to process encoding for TEXT/MTEXT '%s'", textPrefix.c_str()); + } + repeat_last_record(); + return true; } - bool CDxfRead::ReadEllipse() { - double c[3] = {0, 0, 0}; // centre - double m[3] = {0, 0, 0}; // major axis point - double ratio = 0; // ratio of major to minor axis - double start = 0; // start of arc - double end = 0; // end of arc + double centre[3]; + double majorAxisEnd[3]; // relative to centre + double eccentricity = 0; + double startAngleRadians = 0; + double endAngleRadians = 2 * M_PI; - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadEllipse() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found, so finish with Ellipse - ResolveColorIndex(); - OnReadEllipse(c, m, ratio, start, end); - return true; - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; + Setup3DVectorAttribute(10, centre); + Setup3DVectorAttribute(11, majorAxisEnd); + SetupValueAttribute(40, eccentricity); + SetupValueAttribute(41, startAngleRadians); + SetupValueAttribute(42, endAngleRadians); + ProcessAllAttributes(); - case 10: - // centre x - get_line(); - ss.str(m_str); - ss >> c[0]; - c[0] = mm(c[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // centre y - get_line(); - ss.str(m_str); - ss >> c[1]; - c[1] = mm(c[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // centre z - get_line(); - ss.str(m_str); - ss >> c[2]; - c[2] = mm(c[2]); - if (ss.fail()) { - return false; - } - break; - case 11: - // major x - get_line(); - ss.str(m_str); - ss >> m[0]; - m[0] = mm(m[0]); - if (ss.fail()) { - return false; - } - break; - case 21: - // major y - get_line(); - ss.str(m_str); - ss >> m[1]; - m[1] = mm(m[1]); - if (ss.fail()) { - return false; - } - break; - case 31: - // major z - get_line(); - ss.str(m_str); - ss >> m[2]; - m[2] = mm(m[2]); - if (ss.fail()) { - return false; - } - break; - case 40: - // ratio - get_line(); - ss.str(m_str); - ss >> ratio; - if (ss.fail()) { - return false; - } - break; - case 41: - // start - get_line(); - ss.str(m_str); - ss >> start; - if (ss.fail()) { - return false; - } - break; - case 42: - // end - get_line(); - ss.str(m_str); - ss >> end; - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - case 100: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - ResolveColorIndex(); - OnReadEllipse(c, m, ratio, start, end); - return false; -} - - -static bool poly_prev_found = false; -static double poly_prev_x; -static double poly_prev_y; -static double poly_prev_z; -static bool poly_prev_bulge_found = false; -static double poly_prev_bulge; -static bool poly_first_found = false; -static double poly_first_x; -static double poly_first_y; -static double poly_first_z; - -static void -AddPolyLinePoint(CDxfRead* dxf_read, double x, double y, double z, bool bulge_found, double bulge) -{ - - try { - if (poly_prev_found) { - bool arc_done = false; - if (poly_prev_bulge_found) { - double cot = 0.5 * ((1.0 / poly_prev_bulge) - poly_prev_bulge); - double cx = ((poly_prev_x + x) - ((y - poly_prev_y) * cot)) / 2.0; - double cy = ((poly_prev_y + y) + ((x - poly_prev_x) * cot)) / 2.0; - double ps[3] = {poly_prev_x, poly_prev_y, poly_prev_z}; - double pe[3] = {x, y, z}; - double pc[3] = {cx, cy, (poly_prev_z + z) / 2.0}; - dxf_read->OnReadArc(ps, pe, pc, poly_prev_bulge >= 0, false); - arc_done = true; - } - - if (!arc_done) { - double s[3] = {poly_prev_x, poly_prev_y, poly_prev_z}; - double e[3] = {x, y, z}; - dxf_read->OnReadLine(s, e, false); - } - } - - poly_prev_found = true; - poly_prev_x = x; - poly_prev_y = y; - poly_prev_z = z; - if (!poly_first_found) { - poly_first_x = x; - poly_first_y = y; - poly_first_z = z; - poly_first_found = true; - } - poly_prev_bulge_found = bulge_found; - poly_prev_bulge = bulge; - } - catch (...) { - if (!dxf_read->IgnoreErrors()) { - throw; // Re-throw it. - } - } -} - -static void PolyLineStart() -{ - poly_prev_found = false; - poly_first_found = false; + OnReadEllipse(centre, majorAxisEnd, eccentricity, startAngleRadians, endAngleRadians); + return true; } bool CDxfRead::ReadLwPolyLine() { - PolyLineStart(); + VertexInfo currentVertex = {{0, 0, 0}, 0}; + list vertices; + int flags = 0; - bool x_found = false; - bool y_found = false; - double x = 0.0; - double y = 0.0; - double z = 0.0; - bool bulge_found = false; - double bulge = 0.0; - bool closed = false; - int flags; - bool next_item_found = false; + bool have_x = false; + bool have_y = false; - while (!((*m_ifs).eof()) && !next_item_found) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadLwPolyLine() Failed to read integer from '%s'\n", m_str); - return false; + // The documentation for LZPOLYLINE does not specify how you know if you have a new vertex. + // It also does not specify where the bulge, line-width, etc for a particular segment are placed + // relative to the two end vertices. + // We assume here that if we see an X or Y coordinate but we already have the same coordinate, + // a new vertex is starting, and any previous vertex is completely specified. Furthermore, line + // attributes like bulge are placed between the X/Y coordinates for the vertex that starts the + // stroke and the X/Y coordinates for the vertex that ends the stroke or the end of the entity. + // In the latter case the stroke attributes apply to the closure stroke (if any) which ends at + // the first vertex. + Setup3DVectorAttribute(10, currentVertex.location); + SetupScaledDoubleAttribute(42, currentVertex.bulge); + SetupValueAttribute(70, flags); + while (get_next_record() && m_record_type != 0) { + if ((m_record_type == 10 && have_x) || (m_record_type == 21 && have_y)) { + // Starting a new vertex and there is a previous vertex. Save it and init a new one. + vertices.push_back(currentVertex); + currentVertex.location[0] = 0.0; + currentVertex.location[1] = 0.0; + currentVertex.location[2] = 0.0; + currentVertex.bulge = 0.0; + have_x = m_record_type == 10; + have_y = m_record_type == 20; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found - - ResolveColorIndex(); - if (x_found && y_found) { - // add point - AddPolyLinePoint(this, x, y, z, bulge_found, bulge); - bulge_found = false; - x_found = false; - y_found = false; - } - next_item_found = true; - break; - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - case 10: - // x - get_line(); - if (x_found && y_found) { - // add point - AddPolyLinePoint(this, x, y, z, bulge_found, bulge); - bulge_found = false; - x_found = false; - y_found = false; - } - ss.str(m_str); - ss >> x; - x = mm(x); - if (ss.fail()) { - return false; - } - x_found = true; - break; - case 20: - // y - get_line(); - ss.str(m_str); - ss >> y; - y = mm(y); - if (ss.fail()) { - return false; - } - y_found = true; - break; - case 38: - // elevation - get_line(); - ss.str(m_str); - ss >> z; - z = mm(z); - if (ss.fail()) { - return false; - } - break; - case 42: - // bulge - get_line(); - ss.str(m_str); - ss >> bulge; - if (ss.fail()) { - return false; - } - bulge_found = true; - break; - case 70: - // flags - get_line(); - if (sscanf(m_str, "%d", &flags) != 1) { - return false; - } - closed = ((flags & 1) != 0); - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - default: - // skip the next line - get_line(); - break; + else if (m_record_type == 10) { + have_x = true; } + else if (m_record_type == 10) { + have_y = true; + } + ProcessAttribute(); + } + // At the end of the entity if we have vertex information use this as the final vertex. + // (else it was a line with no vertices at all) + if (have_x || have_y) { + vertices.push_back(currentVertex); } - if (next_item_found) { - if (closed && poly_first_found) { - // repeat the first point - ResolveColorIndex(); - AddPolyLinePoint(this, poly_first_x, poly_first_y, poly_first_z, false, 0.0); - } - return true; - } - - return false; + OnReadPolyline(vertices, flags); + repeat_last_record(); + return true; } - -bool CDxfRead::ReadVertex(double* pVertex, bool* bulge_found, double* bulge) -{ - bool x_found = false; - bool y_found = false; - - double x = 0.0; - double y = 0.0; - double z = 0.0; - *bulge = 0.0; - *bulge_found = false; - - pVertex[0] = 0.0; - pVertex[1] = 0.0; - pVertex[2] = 0.0; - - while (!(*m_ifs).eof()) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadVertex() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - ResolveColorIndex(); - put_line(m_str); // read one line too many. put it back. - return (x_found && y_found); - break; - - case 8: // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - - case 10: - // x - get_line(); - ss.str(m_str); - ss >> x; - pVertex[0] = mm(x); - if (ss.fail()) { - return false; - } - x_found = true; - break; - case 20: - // y - get_line(); - ss.str(m_str); - ss >> y; - pVertex[1] = mm(y); - if (ss.fail()) { - return false; - } - y_found = true; - break; - case 30: - // z - get_line(); - ss.str(m_str); - ss >> z; - pVertex[2] = mm(z); - if (ss.fail()) { - return false; - } - break; - - case 42: - get_line(); - *bulge_found = true; - ss.str(m_str); - ss >> *bulge; - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - - default: - // skip the next line - get_line(); - break; - } - } - - return false; -} - - bool CDxfRead::ReadPolyLine() { - PolyLineStart(); + VertexInfo currentVertex = {{0, 0, 0}, 0}; + list vertices; + int flags = 0; - bool closed = false; - int flags; - bool first_vertex_section_found = false; - double first_vertex[3] = {0, 0, 0}; - bool bulge_found; - double bulge; + SetupValueAttribute(70, flags); + ProcessAllAttributes(); - while (!(*m_ifs).eof()) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadPolyLine() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found - ResolveColorIndex(); - get_line(); - if (!strcmp(m_str, "VERTEX")) { - double vertex[3] = {0, 0, 0}; - if (CDxfRead::ReadVertex(vertex, &bulge_found, &bulge)) { - if (!first_vertex_section_found) { - first_vertex_section_found = true; - memcpy(first_vertex, vertex, 3 * sizeof(double)); - } - AddPolyLinePoint(this, vertex[0], vertex[1], vertex[2], bulge_found, bulge); - break; - } - } - if (!strcmp(m_str, "SEQEND")) { - if (closed && first_vertex_section_found) { - AddPolyLinePoint(this, - first_vertex[0], - first_vertex[1], - first_vertex[2], - 0, - 0); - } - first_vertex_section_found = false; - PolyLineStart(); - return (true); - } - break; - case 70: - // flags - get_line(); - if (sscanf(m_str, "%d", &flags) != 1) { - return false; - } - closed = ((flags & 1) != 0); - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - default: - // skip the next line - get_line(); - break; - } + // We are now followed by a series of VERTEX entities followed by ENDSEQ. + // To avoid eating and discarding the rest of the entieies if ENDSEQ is missing, + // we quit on any unknown type-0 record. + Setup3DVectorAttribute(10, currentVertex.location); + SetupScaledDoubleAttribute(42, currentVertex.bulge); + while (get_next_record() && m_record_type == 0 && !strcmp(m_record_data, "VERTEX")) { + // Set vertex defaults + currentVertex.location[0] = 0.0; + currentVertex.location[1] = 0.0; + currentVertex.location[2] = 0.0; + currentVertex.bulge = 0.0; + ProcessAllAttributes(); + vertices.push_back(currentVertex); + } + if (strcmp(m_record_data, "SEQEND")) { + ImportError("POLYLINE ends with '%s' record rather than 'SEQEND'\n", m_record_data); + repeat_last_record(); } - return false; + OnReadPolyline(vertices, flags); + return true; } +bool CDxfRead::ReadInsert() +{ + double center[3]; + double scale[3] = {1, 1, 1}; + double rotationDegrees = 0.0; + char blockName[1024] = {0}; + + Setup3DVectorAttribute(10, center); + SetupValueAttribute(41, scale[0]); + SetupValueAttribute(42, scale[1]); + SetupValueAttribute(43, scale[2]); + SetupValueAttribute(50, rotationDegrees); + SetupStringAttribute(2, blockName); + ProcessAllAttributes(); + OnReadInsert(center, scale, blockName, rotationDegrees * M_PI / 180); + return (true); +} + +bool CDxfRead::ReadDimension() +{ + double start[3]; + double end[3]; + double linePosition[3]; + double textPosition[3]; + double rotation = 0; + int dimensionType = 0; // This appears to default to zero + + // Per documentation: + // 10 is the "dimension line location" + // 11 is the midpoint of the dimension text + // 13 is the start point of the 1st extension line + // 14 is the start point of the 2nd extension line + // 50 is the rotation of the dimension (the direction of the dimension line) + // 52 (if present) is the angle relative to the dimension line for the extension lines; default + // 90 degrees + Setup3DVectorAttribute(13, start); // WCS + Setup3DVectorAttribute(14, end); // WCS + Setup3DVectorAttribute(10, linePosition); // WCS + Setup3DVectorAttribute(11, textPosition); // OCS + SetupValueAttribute(50, rotation); + SetupValueAttribute(70, dimensionType); + ProcessAllAttributes(); + + dimensionType &= ~0xE0; // Remove flags + switch (dimensionType) { + case 0: + case 1: + OnReadDimension(start, end, linePosition, rotation * M_PI / 180); + break; + default: + UnsupportedFeature("Dimension type '%d'", dimensionType); + break; + } + return (true); +} + +bool CDxfRead::ReadUnknownEntity() +{ + UnsupportedFeature("Entity type '%s'", m_record_data); + ProcessAllAttributes(); + return true; +} + +bool CDxfRead::ReadBlockInfo() +{ + int blockType = 0; + InitializeAttributes(); + // Both 2 and 3 are the block name. + SetupStringAttribute(2, m_block_name); + SetupStringAttribute(3, m_block_name); + SetupValueAttribute(70, blockType); + ProcessAllAttributes(); + + // Read the entities in the block definition. + // These are processed just like in-drawing entities but the code can + // recognize that we are in the BLOCKS section, and which block we are defining, + // by looking at the result of LayerName() + // Note that the Layer Name in the block entities is ignored even though it + // should be used to resolve BYLAYER attributes and also for placing the entity + // when the block is inserted. + if (blockType & 0x04) { + // Note that this doesn't mean there are not entities in the block. I don't + // know if the external reference can be cached because there are two other bits + // here, 0x10 and 0x20, that seem to handle "resolved" external references. + UnsupportedFeature("External (xref) BLOCK"); + } + while (get_next_record() && m_record_type != 0 && strcmp(m_record_data, "ENDBLK")) { + if (blockType & 0x01) { + // It is an anonymous block used to build dimensions, hatches, etc so we don't need it + // and don't want to complaining about unhandled entity types. + while (get_next_record() && m_record_type != 0) {} + repeat_last_record(); + } + else if (IgnoreErrors()) { + try { + if (!ReadEntity()) { + return false; + } + } + catch (...) { + } + } + else { + if (!ReadEntity()) { + return false; + } + } + } + return true; +} + +void CDxfRead::UnsupportedFeature(const char* format, ...) +{ + // Upcoming C++20 std::format might simplify some of this code + va_list args; + std::string formattedMessage; + + va_start(args, format); + // Make room in formattedMessage for the formatted text and its terminating null byte + formattedMessage.resize((size_t)vsnprintf(nullptr, 0, format, args) + 1); + va_end(args); + va_start(args, format); + vsprintf(formattedMessage.data(), format, args); + va_end(args); + // Remove the null byte + formattedMessage.pop_back(); + + // 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; + } +} + +bool CDxfRead::get_next_record() +{ + if (m_repeat_last_record) { + m_repeat_last_record = false; + return m_not_eof; + } + + if ((*m_ifs).eof()) { + m_not_eof = false; + return false; + } + + m_ifs->getline(m_record_data, 1024); + ++m_line; + if (sscanf(m_record_data, "%d", &m_record_type) != 1) { + ImportError("CDxfRead::get_next_record() Failed to get integer record type from '%s'\n", + m_record_data); + return (false); + } + if ((*m_ifs).eof()) { + return false; + } + + m_ifs->getline(m_record_data, 1024); + ++m_line; + // Remove any carriage return at the end of m_str which may occur because of inconsistent + // handling of LF vs. CRLF line termination. + size_t len = strlen(m_record_data); + if (len > 0 && m_record_data[len - 1] == '\r') { + m_record_data[len - 1] = '\0'; + } + // The code that was here just blindly trimmed leading white space, but if you have, for + // instance, a TEXT entity whose text starts with spaces, or, more plausibly, a long TEXT entity + // where the text is broken into one or more type-3 records with a final type-1 and the break + // happens to be just before a space, this would be wrong. + return true; +} + +void CDxfRead::repeat_last_record() +{ + m_repeat_last_record = true; +} + +bool CDxfRead::ReadUnits() +{ + get_next_record(); // Get the value for the variable + int n = 0; + if (sscanf(m_record_data, "%d", &n) == 1) { + m_eUnits = eDxfUnits_t(n); + return (true); + } // End if - then + else { + ImportError("CDxfRead::ReadUnits() Failed to get integer from '%s'\n", m_record_data); + return (false); + } +} + +// +// Intercepts for On... calls to derived class +// (These have distinct signatures from the ones they call) +bool CDxfRead::OnReadPolyline(std::list& vertices, int flags) +{ + if (vertices.size() < 2) { + // TODO: Warning + return true; + } + + bool closed = ((flags & 1) != 0); + auto startVertex = vertices.end(); + if (closed) { + // If the shape is closed, point at the last vertex. The first stroke drawn will be the + // closure. + --startVertex; + } + for (auto endVertex = vertices.begin(); endVertex != vertices.end(); endVertex++) { + if (startVertex != vertices.end()) { + if (startVertex->bulge != 0.0) { + // Bulge is 1/4 tan(arc angle), positive for CCW arc. + double cot = 0.5 * ((1.0 / startVertex->bulge) - startVertex->bulge); + double cx = ((startVertex->location[0] + endVertex->location[0]) + - ((endVertex->location[1] - startVertex->location[1]) * cot)) + / 2.0; + double cy = ((startVertex->location[1] + endVertex->location[1]) + + ((endVertex->location[0] - startVertex->location[0]) * cot)) + / 2.0; + double pc[3] = {cx, cy, (startVertex->location[2] + endVertex->location[2]) / 2.0}; + OnReadArc(startVertex->location, + endVertex->location, + pc, + startVertex->bulge >= 0, + false); + } + else { + OnReadLine(startVertex->location, endVertex->location, false); + } + } + startVertex = endVertex; + } + return true; +} void CDxfRead::OnReadArc(double start_angle, double end_angle, double radius, - const double* c, + const double* center, double z_extrusion_dir, bool hidden) { double s[3] = {0, 0, 0}, e[3] = {0, 0, 0}, temp[3] = {0, 0, 0}; if (z_extrusion_dir == 1.0) { - temp[0] = c[0]; - temp[1] = c[1]; - temp[2] = c[2]; - s[0] = c[0] + radius * cos(start_angle * M_PI / 180); - s[1] = c[1] + radius * sin(start_angle * M_PI / 180); - s[2] = c[2]; - e[0] = c[0] + radius * cos(end_angle * M_PI / 180); - e[1] = c[1] + radius * sin(end_angle * M_PI / 180); - e[2] = c[2]; + temp[0] = center[0]; + temp[1] = center[1]; + temp[2] = center[2]; + s[0] = center[0] + radius * cos(start_angle * M_PI / 180); + s[1] = center[1] + radius * sin(start_angle * M_PI / 180); + s[2] = center[2]; + e[0] = center[0] + radius * cos(end_angle * M_PI / 180); + e[1] = center[1] + radius * sin(end_angle * M_PI / 180); + e[2] = center[2]; } else { - temp[0] = -c[0]; - temp[1] = c[1]; - temp[2] = c[2]; + temp[0] = -center[0]; + temp[1] = center[1]; + temp[2] = center[2]; - e[0] = -(c[0] + radius * cos(start_angle * M_PI / 180)); - e[1] = (c[1] + radius * sin(start_angle * M_PI / 180)); - e[2] = c[2]; - s[0] = -(c[0] + radius * cos(end_angle * M_PI / 180)); - s[1] = (c[1] + radius * sin(end_angle * M_PI / 180)); - s[2] = c[2]; + e[0] = -(center[0] + radius * cos(start_angle * M_PI / 180)); + e[1] = (center[1] + radius * sin(start_angle * M_PI / 180)); + e[2] = center[2]; + s[0] = -(center[0] + radius * cos(end_angle * M_PI / 180)); + s[1] = (center[1] + radius * sin(end_angle * M_PI / 180)); + s[2] = center[2]; } OnReadArc(s, e, temp, true, hidden); } -void CDxfRead::OnReadCircle(const double* c, double radius, bool hidden) +void CDxfRead::OnReadCircle(const double* center, double radius, bool hidden) { - double s[3]; - double start_angle = 0; - s[0] = c[0] + radius * cos(start_angle * M_PI / 180); - s[1] = c[1] + radius * sin(start_angle * M_PI / 180); - s[2] = c[2]; + // OnReadCircle wants a start point, so we pick an arbitrary point on the circunference + double s[3] = {center[0] + radius, center[1], center[2]}; OnReadCircle(s, - c, + center, false, hidden); // false to change direction because otherwise the arc length is zero } -void CDxfRead::OnReadEllipse(const double* c, +void CDxfRead::OnReadEllipse(const double* center, const double* m, double ratio, double start_angle, @@ -3347,434 +2524,7 @@ void CDxfRead::OnReadEllipse(const double* c, double rotation = atan2(m[1] / major_radius, m[0] / major_radius); - OnReadEllipse(c, major_radius, minor_radius, rotation, start_angle, end_angle, true); -} - - -bool CDxfRead::ReadInsert() -{ - double c[3] = {0, 0, 0}; // coordinate - double s[3] = {1, 1, 1}; // scale - double rot = 0.0; // rotation - char name[1024] = {0}; - - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadInsert() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found - ResolveColorIndex(); - OnReadInsert(c, s, name, rot * M_PI / 180); - return (true); - case 8: - // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - case 10: - // coord x - get_line(); - ss.str(m_str); - ss >> c[0]; - c[0] = mm(c[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // coord y - get_line(); - ss.str(m_str); - ss >> c[1]; - c[1] = mm(c[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // coord z - get_line(); - ss.str(m_str); - ss >> c[2]; - c[2] = mm(c[2]); - if (ss.fail()) { - return false; - } - break; - case 41: - // scale x - get_line(); - ss.str(m_str); - ss >> s[0]; - if (ss.fail()) { - return false; - } - break; - case 42: - // scale y - get_line(); - ss.str(m_str); - ss >> s[1]; - if (ss.fail()) { - return false; - } - break; - case 43: - // scale z - get_line(); - ss.str(m_str); - ss >> s[2]; - if (ss.fail()) { - return false; - } - break; - case 50: - // rotation - get_line(); - ss.str(m_str); - ss >> rot; - if (ss.fail()) { - return false; - } - break; - case 2: - // block name - get_line(); - strcpy(name, m_str); - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - return false; -} - - -bool CDxfRead::ReadDimension() -{ - double s[3] = {0, 0, 0}; // startpoint - double e[3] = {0, 0, 0}; // endpoint - double p[3] = {0, 0, 0}; // dimpoint - double rot = -1.0; // rotation - - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadInsert() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: - // next item found - ResolveColorIndex(); - OnReadDimension(s, e, p, rot * M_PI / 180); - return (true); - case 8: - // Layer name follows - get_line(); - strcpy(m_layer_name, m_str); - break; - case 13: - // start x - get_line(); - ss.str(m_str); - ss >> s[0]; - s[0] = mm(s[0]); - if (ss.fail()) { - return false; - } - break; - case 23: - // start y - get_line(); - ss.str(m_str); - ss >> s[1]; - s[1] = mm(s[1]); - if (ss.fail()) { - return false; - } - break; - case 33: - // start z - get_line(); - ss.str(m_str); - ss >> s[2]; - s[2] = mm(s[2]); - if (ss.fail()) { - return false; - } - break; - case 14: - // end x - get_line(); - ss.str(m_str); - ss >> e[0]; - e[0] = mm(e[0]); - if (ss.fail()) { - return false; - } - break; - case 24: - // end y - get_line(); - ss.str(m_str); - ss >> e[1]; - e[1] = mm(e[1]); - if (ss.fail()) { - return false; - } - break; - case 34: - // end z - get_line(); - ss.str(m_str); - ss >> e[2]; - e[2] = mm(e[2]); - if (ss.fail()) { - return false; - } - break; - case 10: - // dimline x - get_line(); - ss.str(m_str); - ss >> p[0]; - p[0] = mm(p[0]); - if (ss.fail()) { - return false; - } - break; - case 20: - // dimline y - get_line(); - ss.str(m_str); - ss >> p[1]; - p[1] = mm(p[1]); - if (ss.fail()) { - return false; - } - break; - case 30: - // dimline z - get_line(); - ss.str(m_str); - ss >> p[2]; - p[2] = mm(p[2]); - if (ss.fail()) { - return false; - } - break; - case 50: - // rotation - get_line(); - ss.str(m_str); - ss >> rot; - if (ss.fail()) { - return false; - } - break; - case 62: - // color index - get_line(); - ss.str(m_str); - ss >> m_ColorIndex; - if (ss.fail()) { - return false; - } - break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - return false; -} - - -bool CDxfRead::ReadBlockInfo() -{ - while (!((*m_ifs).eof())) { - get_line(); - int n; - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadBlockInfo() Failed to read integer from '%s'\n", m_str); - return false; - } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 2: - // block name - get_line(); - strcpy(m_block_name, m_str); - return true; - case 3: - // block name too??? - get_line(); - strcpy(m_block_name, m_str); - return true; - default: - // skip the next line - get_line(); - break; - } - } - return false; -} - - -void CDxfRead::get_line() -{ - if (m_unused_line[0] != '\0') { - strcpy(m_str, m_unused_line); - memset(m_unused_line, '\0', sizeof(m_unused_line)); - return; - } - - m_ifs->getline(m_str, 1024); - - char str[1024]; - size_t len = strlen(m_str); - int j = 0; - bool non_white_found = false; - for (size_t i = 0; i < len; i++) { - if (non_white_found || (m_str[i] != ' ' && m_str[i] != '\t')) { - if (m_str[i] != '\r') { - str[j] = m_str[i]; - j++; - } - non_white_found = true; - } - } - str[j] = 0; - strcpy(m_str, str); -} - -void dxf_strncpy(char* dst, const char* src, size_t size) -{ - size_t ret = strlen(src); - - if (size) { - size_t len = (ret >= size) ? size - 1 : ret; - memcpy(dst, src, len); - dst[len] = '\0'; - } -} - -void CDxfRead::put_line(const char* value) -{ - dxf_strncpy(m_unused_line, value, sizeof(m_unused_line)); -} - - -bool CDxfRead::ReadUnits() -{ - get_line(); // Skip to next line. - get_line(); // Skip to next line. - int n = 0; - if (sscanf(m_str, "%d", &n) == 1) { - m_eUnits = eDxfUnits_t(n); - return (true); - } // End if - then - else { - printf("CDxfRead::ReadUnits() Failed to get integer from '%s'\n", m_str); - return (false); - } -} - - -bool CDxfRead::ReadLayer() -{ - std::string layername; - ColorIndex_t colorIndex = -1; - - while (!((*m_ifs).eof())) { - get_line(); - int n; - - if (sscanf(m_str, "%d", &n) != 1) { - printf("CDxfRead::ReadLayer() Failed to read integer from '%s'\n", m_str); - return false; - } - - std::istringstream ss; - ss.imbue(std::locale("C")); - switch (n) { - case 0: // next item found, so finish with line - if (layername.empty()) { - printf("CDxfRead::ReadLayer() - no layer name\n"); - return false; - } - m_layer_ColorIndex_map[layername] = colorIndex; - return true; - - case 2: // Layer name follows - get_line(); - layername = m_str; - break; - - case 62: - // layer color ; if negative, layer is off - get_line(); - if (sscanf(m_str, "%d", &colorIndex) != 1) { - return false; - } - break; - - case 6: // linetype name - case 70: // layer flags - case 100: - case 290: - case 370: - case 390: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; - } - } - return false; + OnReadEllipse(center, major_radius, minor_radius, rotation, start_angle, end_angle, true); } bool CDxfRead::ReadVersion() @@ -3793,16 +2543,15 @@ bool CDxfRead::ReadVersion() "AC1032"}; assert(VersionNames.size() == RNewer - ROlder - 1); - get_line(); - get_line(); + get_next_record(); // Get the value for the variable std::vector::const_iterator first = VersionNames.cbegin(); std::vector::const_iterator last = VersionNames.cend(); - std::vector::const_iterator found = std::lower_bound(first, last, m_str); + std::vector::const_iterator found = std::lower_bound(first, last, m_record_data); if (found == last) { m_version = RNewer; } - else if (*found == m_str) { - m_version = (eDXFVersion_t)(std::distance(first, found) + (ROlder + 1)); + else if (*found == m_record_data) { + m_version = (eDXFVersion_t)((int)std::distance(first, found) + (ROlder + 1)); } else if (found == first) { m_version = ROlder; @@ -3816,11 +2565,10 @@ bool CDxfRead::ReadVersion() bool CDxfRead::ReadDWGCodePage() { - get_line(); - get_line(); + get_next_record(); // Get the value for the variable assert(m_CodePage == nullptr); // If not, we have found two DWGCODEPAGE variables or DoRead was // called twice on the same CDxfRead object. - m_CodePage = new std::string(m_str); + m_CodePage = new std::string(m_record_data); return ResolveEncoding(); } @@ -3843,12 +2591,10 @@ bool CDxfRead::ResolveEncoding() else { // Codepage names may be of the form "ansi_1252" which we map to "cp1252" but we don't map // "ansi_x3xxxx" (which happens to mean "ascii") + // Also some DXF files have the codepage name in uppercase so we lowercase it. std::string* p = new std::string(*m_CodePage); - std::string p_lower; - for (std::string::const_iterator i = p->begin(); i != p->end(); ++i) { - p_lower += tolower(*i); - } - if (p_lower.substr(0, 5) == "ansi_" && p_lower.substr(0, 7) != "ansi_x3") { + std::transform(p->begin(), p->end(), p->begin(), ::tolower); + if (strncmp(p->c_str(), "ansi_", 5) == 0 && strncmp(p->c_str(), "ansi_x3", 7) != 0) { p->replace(0, 5, "cp"); } m_encoding = p; @@ -3914,199 +2660,434 @@ void CDxfRead::DoRead(const bool ignore_errors /* = false */) return; } - get_line(); + // Loop reading and identifying the sections. + while (get_next_record()) { + if (m_record_type != 0) { + ImportError("Found type %d record when expecting start of a SECTION or EOF\n", + m_record_type); + continue; + } + if (!strcmp(m_record_data, "EOF")) { // TODO: Check for drivel beyond EOF record + break; + } + if (strcmp(m_record_data, "SECTION")) { + ImportError("Found %s record when expecting start of a SECTION\n", m_record_data); + continue; + } - while (!((*m_ifs).eof())) { - if (!strcmp(m_str, "$INSUNITS")) { - if (!ReadUnits()) { + if (!get_next_record()) { + ImportError("Unclosed SECTION at end of file\n"); + return; + } + if (m_record_type != 2) { + ImportError("Ignored SECTION with no name record\n"); + if (!ReadIgnoredSection()) { return; } - continue; - } // End if - then + } + else if (!strcmp(m_record_data, "HEADER")) { + if (!ReadHeaderSection()) { + return; + } + } + else if (!strcmp(m_record_data, "TABLES")) { + if (!ReadTablesSection()) { + return; + } + } + else if (!strcmp(m_record_data, "BLOCKS")) { + if (!ReadBlocksSection()) { + return; + } + } + else if (!strcmp(m_record_data, "ENTITIES")) { + if (!ReadEntitiesSection()) { + return; + } + } + else { + if (!ReadIgnoredSection()) { + return; + } + } + } + AddGraphics(); - if (!strcmp(m_str, "$MEASUREMENT")) { - get_line(); - get_line(); + // FLush out any unsupported features messages + auto i = m_unsupportedFeaturesNoted.begin(); + if (i != m_unsupportedFeaturesNoted.end()) { + ImportError("Unsupported DXF features:\n"); + do { + ImportError("%s: %d time(s) first at line %d\n", + i->first, + i->second.first, + i->second.second); + } while (++i != m_unsupportedFeaturesNoted.end()); + } +} + +bool CDxfRead::ReadEntity() +{ + InitializeCommonEntityAttributes(); + // The entity record is already the current record and is already checked as a type 0 record + if (!strcmp(m_record_data, "LINE")) { + return ReadLine(); + } + else if (!strcmp(m_record_data, "ARC")) { + return ReadArc(); + } + else if (!strcmp(m_record_data, "CIRCLE")) { + return ReadCircle(); + } + else if (!strcmp(m_record_data, "MTEXT")) { + return ReadText(); + } + else if (!strcmp(m_record_data, "TEXT")) { + return ReadText(); + } + else if (!strcmp(m_record_data, "ELLIPSE")) { + return ReadEllipse(); + } + else if (!strcmp(m_record_data, "SPLINE")) { + return ReadSpline(); + } + else if (!strcmp(m_record_data, "LWPOLYLINE")) { + return ReadLwPolyLine(); + } + else if (!strcmp(m_record_data, "POLYLINE")) { + return ReadPolyLine(); + } + else if (!strcmp(m_record_data, "POINT")) { + return ReadPoint(); + } + else if (!strcmp(m_record_data, "INSERT")) { + return ReadInsert(); + } + else if (!strcmp(m_record_data, "DIMENSION")) { + return ReadDimension(); + } + else { + return ReadUnknownEntity(); + } +} + +bool CDxfRead::ReadHeaderSection() +{ + // Read to the next ENDSEC record marking the end of the section. + // This section contains variables, most of which we ignore. Each one is a type-9 record giving + // the variable name, followed by a single record giving the value; the record type depends on + // the variable's data type. + while (get_next_record()) { + if (m_record_type == 0 && !strcmp(m_record_data, "ENDSEC")) { + return true; + } + else if (m_record_type != 9) { + continue; // Quietly ignore unknown record types + } + if (!strcmp(m_record_data, "$INSUNITS")) { + if (!ReadUnits()) { + return false; + } + } + else if (!strcmp(m_record_data, "$MEASUREMENT")) { + get_next_record(); int n = 1; - if (sscanf(m_str, "%d", &n) == 1) { + if (sscanf(m_record_data, "%d", &n) == 1) { if (n == 0) { m_measurement_inch = true; } } - continue; - } // End if - then - - if (!strcmp(m_str, "$ACADVER")) { + } + else if (!strcmp(m_record_data, "$ACADVER")) { if (!ReadVersion()) { - return; - } - continue; - } // End if - then - - if (!strcmp(m_str, "$DWGCODEPAGE")) { - if (!ReadDWGCodePage()) { - return; - } - continue; - } // End if - then - - if (!strcmp(m_str, "0")) { - get_line(); - if (!strcmp(m_str, "SECTION")) { - strcpy(m_section_name, ""); - get_line(); - get_line(); - if (strcmp(m_str, "ENTITIES")) { - strcpy(m_section_name, m_str); - } - strcpy(m_block_name, ""); - - } // End if - then - else if (!strcmp(m_str, "TABLE")) { - get_line(); - get_line(); - } - - else if (!strcmp(m_str, "LAYER")) { - get_line(); - get_line(); - if (!ReadLayer()) { - printf("CDxfRead::DoRead() Failed to read layer\n"); - // return; Some objects or tables can have "LAYER" as name... - } - continue; - } - - else if (!strcmp(m_str, "BLOCK")) { - if (!ReadBlockInfo()) { - printf("CDxfRead::DoRead() Failed to read block info\n"); - return; - } - continue; - } // End if - then - - else if (!strcmp(m_str, "ENDSEC")) { - strcpy(m_section_name, ""); - strcpy(m_block_name, ""); - } // End if - then - else if (!strcmp(m_str, "LINE")) { - if (!ReadLine()) { - printf("CDxfRead::DoRead() Failed to read line\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "ARC")) { - if (!ReadArc()) { - printf("CDxfRead::DoRead() Failed to read arc\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "CIRCLE")) { - if (!ReadCircle()) { - printf("CDxfRead::DoRead() Failed to read circle\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "MTEXT")) { - if (!ReadText()) { - printf("CDxfRead::DoRead() Failed to read text\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "TEXT")) { - if (!ReadText()) { - printf("CDxfRead::DoRead() Failed to read text\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "ELLIPSE")) { - if (!ReadEllipse()) { - printf("CDxfRead::DoRead() Failed to read ellipse\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "SPLINE")) { - if (!ReadSpline()) { - printf("CDxfRead::DoRead() Failed to read spline\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "LWPOLYLINE")) { - if (!ReadLwPolyLine()) { - printf("CDxfRead::DoRead() Failed to read LW Polyline\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "POLYLINE")) { - if (!ReadPolyLine()) { - printf("CDxfRead::DoRead() Failed to read Polyline\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "POINT")) { - if (!ReadPoint()) { - printf("CDxfRead::DoRead() Failed to read Point\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "INSERT")) { - if (!ReadInsert()) { - printf("CDxfRead::DoRead() Failed to read Insert\n"); - return; - } - continue; - } - else if (!strcmp(m_str, "DIMENSION")) { - if (!ReadDimension()) { - printf("CDxfRead::DoRead() Failed to read Dimension\n"); - return; - } - continue; + return false; + } + } + else if (!strcmp(m_record_data, "$DWGCODEPAGE")) { + if (!ReadDWGCodePage()) { + return false; + } + } + else { + // any other variable, skip its value + if (!get_next_record()) { + return false; } } - - get_line(); } - AddGraphics(); + return false; } - -void CDxfRead::ResolveColorIndex() +bool CDxfRead::ReadTablesSection() { - - if (m_ColorIndex == ColorBylayer) // if color = layer color, replace by color from layer - { - m_ColorIndex = m_layer_ColorIndex_map[std::string(m_layer_name)]; + // Read to the next ENDSEC record marking the end of the section. + // This section contains various tables, many of which we ignore. Each one is a type-0 TABLE + // record followed by a type-2 (name) record giving the table name, followed by the table + // contents. Each set of contents is terminates by the next type-0 TABLE or ENDSEC directive. + while (get_next_record()) { + if (m_record_type != 0) { + continue; // Ignore any non-type-0 contents in the section. + } + if (!strcmp(m_record_data, "ENDSEC")) { + return true; + } + if (strcmp(m_record_data, "TABLE")) { + continue; // Ignore any type-0 non-TABLE contents in the section + } + get_next_record(); + if (m_record_type != 2) { + ImportError("Found unexpected type %d record instead of table name\n", m_record_type); + } + else if (!strcmp(m_record_data, "LAYER")) { + if (!ReadLayerTable()) { + return false; + } + } + else { + if (!ReadIgnoredTable()) { + return false; + } + } } + return false; +} + +bool CDxfRead::ReadIgnoredSection() +{ + // Read to the next ENDSEC record marking the end of the section. + while (get_next_record()) { + if (m_record_type == 0 && !strcmp(m_record_data, "ENDSEC")) { + return true; + } + } + return false; +} + +bool CDxfRead::ReadBlocksSection() +{ + // Read to the next ENDSEC record marking the end of the section. + // Within this section we should find type-0 BLOCK groups + while (get_next_record()) { + if (m_record_type != 0) { + continue; // quietly ignore non-type-0 records; + } + if (!strcmp(m_record_data, "ENDSEC")) { + // End of section + return true; + } + if (strcmp(m_record_data, "BLOCK")) { + continue; // quietly ignore non-BLOCK records + } + if (!ReadBlockInfo()) { + ImportError("CDxfRead::DoRead() Failed to read block\n"); + } + m_block_name[0] = '\0'; + } + + return false; +} + +bool CDxfRead::ReadEntitiesSection() +{ + // Read to the next ENDSEC record marking the end of the section. + // Within this section we should find type-0 BLOCK groups + while (get_next_record()) { + if (m_record_type != 0) { + continue; // quietly ignore non-type-0 records; + } + if (!strcmp(m_record_data, "ENDSEC")) { + // End of section + return true; + } + if (IgnoreErrors()) { + try { + if (!ReadEntity()) { + return false; + } + } + catch (...) { + } + } + else { + if (!ReadEntity()) { + return false; + } + } + } + + return false; +} + +bool CDxfRead::ReadLayer() +{ + std::string layername; + ColorIndex_t layerColor = 0; + int layerFlags = 0; + InitializeAttributes(); + + SetupStringAttribute(2, layername); + SetupValueAttribute(62, layerColor); + SetupValueAttribute(70, layerFlags); + ProcessAllAttributes(); + if (layername.empty()) { + ImportError("CDxfRead::ReadLayer() - no layer name\n"); + return false; + } + if (layerFlags & 0x01) { + // Frozen layers are implicitly hidden which we don't do yet. + // TODO: Should have an import option to omit frozen layers. + UnsupportedFeature("Frozen layers"); + } + if (layerColor < 0) { + UnsupportedFeature("Hidden layers"); + layerColor = -layerColor; + } + m_layer_ColorIndex_map[layername] = layerColor; + return true; +} +bool CDxfRead::ReadLayerTable() +{ + // Read to the next TABLE record indicating another table in the TABLES section, or to the + // ENDSEC record marking the end of the TABLES section. This table contains a series of type-0 + // LAYER groups + while (get_next_record()) { + if (m_record_type != 0) { + continue; // quietly ignore non-type-0 records; this table has some preamble + } + if (!strcmp(m_record_data, "TABLE") || !strcmp(m_record_data, "ENDSEC")) { + // End of table + repeat_last_record(); + return true; + } + if (strcmp(m_record_data, "LAYER")) { + continue; // quietly ignore non-LAYER records + } + if (!ReadLayer()) { + ImportError("CDxfRead::DoRead() Failed to read layer\n"); + } + continue; + } + + return false; +} + +bool CDxfRead::ReadIgnoredTable() +{ + // Read to the next TABLE record indicating another table in the TABLES section, or to the + // ENDSEC record marking the end of the TABLES section. + while (get_next_record()) { + if (m_record_type == 0 + && (!strcmp(m_record_data, "TABLE") || !strcmp(m_record_data, "ENDSEC"))) { + repeat_last_record(); + return true; + } + } + return false; } std::string CDxfRead::LayerName() const { std::string result; - if (strlen(m_section_name) > 0) { - result.append(m_section_name); - result.append(" "); - } - if (strlen(m_block_name) > 0) { + result.append("BLOCKS "); result.append(m_block_name); result.append(" "); } - if (strlen(m_layer_name) > 0) { + else if (strlen(m_layer_name) > 0) { + result.append("ENTITIES "); result.append(m_layer_name); } return (result); } + +inline static double level(int distance, double blackLevel) +{ + // Distance is the number of 24ths around the color wheel between the desired hue and + // the primary hue in question. Coming in it is a ordinate difference and so can be negative + // so the first thing we do is take its absolute value. + if (distance < 0) { + distance = -distance; + } + // If the distance is greater than 12, it is measuring the long way around the color wheel so we + // reduce it to measuring along the short way instead + if (distance > 12) { + distance = 24 - distance; + } + if (distance <= 4) { + // A distance 4 or less givs full intensity of the primary color + return 1.0; + } + else if (distance < 8) { + // Between 4 and 8 gives a blend of the full primary and the black level + return ((8 - distance) + blackLevel * (distance - 4)) / 4; + } + else { + // 8 and beyond yield the black level + return blackLevel; + } +} +inline static App::Color wheel(int hue, double blackLevel, double multiplier = 1.0) +{ + return App::Color(level(hue - 0, blackLevel) * multiplier, + level(hue - 8, blackLevel) * multiplier, + level(hue - 16, blackLevel) * multiplier); +} +App::Color CDxfRead::ObjectColor() const +{ + int index = m_ColorIndex; + if (index == ColorBylayer) // if color = layer color, replace by color from layer + { + auto key = std::string(m_layer_name); + index = m_layer_ColorIndex_map.count(key) > 0 ? m_layer_ColorIndex_map.at(key) : 0; + } + // TODO: If it is ColorByBlock we need to use the color of the INSERT entity. + // This is tricky because a block can itself contain INSERT entities and we don't currently + // record the required information. IIRC INSERT in a block will do something strange like + // try to insert the block into the main drawing instead of into the block being defined. + + // The first 7 colors (1-7) have ad hoc names red, yellow, green, cyan, blue, magenta, and + // black. 8, 9, 250-254 are lightening shades of gray. These are rendered by the app in a manner + // to contrast with the background color. + // For others, (color/10) determines the hue around the + // color circle, with even numbers fading to black on the tens digit, + // and odd numberd being blended with AA and again fading to black. + // The fade is FF BD 81 68 4F (100%, 74%, 50%, 40%, 30%) indexed by (index/2)%5 + // The AA fades as AA 7E 56 45 35 which is almost the exact same percentages. + // For hue, (index-10)/10 : 0 is ff0000, and each step linearly adds green until 4 is pure + // yellow ffff00, then red starts to fade... until but not including 24 which is back to ff0000. + App::Color result = App::Color(); + if (index == 0) { + // Technically, 0 is BYBLOCK and not a real color, but all that means is that an object in a + // block cannot specifically ask to be black. These colors are all contrasted to the + // background so there is no objective black colour, through 255 is an objective white. + result = App::Color(); + } + else if (index < 7) { + result = wheel((index - 1) * 4, 0x00); + } + else if (index == 7) { + result = App::Color(1, 1, 1); + } + else if (index == 8) { + result = App::Color(0.5, 0.5, 0.5); + } + else if (index == 9) { + result = App::Color(0.75, 0.75, 0.75); + } + else if (index >= 250) { + int brightness = (index - 250 + (255 - index) * 0.2) / 5; + result = App::Color(brightness, brightness, brightness); + } + else { + int fade = (index / 2) % 5; + static double fades[5] = {1.00, 0.74, 0.50, 0.40, 0.30}; + return wheel(index / 10 - 1, (index & 1) ? 0.69 : 0, fades[fade]); + } + // TODO: These colors are modified to contrast with the background. In the original program + // this is just a rendering feature, but FreeCAD does not support this so the user has the + // option of modifying the colors to contrast with the background at time of import. + return result; +} diff --git a/src/Mod/Import/App/dxf/dxf.h b/src/Mod/Import/App/dxf/dxf.h index 260c218b40..40479be575 100644 --- a/src/Mod/Import/App/dxf/dxf.h +++ b/src/Mod/Import/App/dxf/dxf.h @@ -22,6 +22,8 @@ #include #include +#include +#include #include @@ -56,12 +58,12 @@ typedef enum // spline data for reading struct SplineData { - double norm[3]; - int degree; - int knots; - int control_points; - int fit_points; - int flag; + double norm[3] = {0, 0, 0}; + int degree = 0; + int knots = 0; + int control_points = 0; + int fit_points = 0; + int flag = 0; std::list starttanx; std::list starttany; std::list starttanz; @@ -199,7 +201,7 @@ protected: std::vector m_blkRecordList; public: - CDxfWrite(const char* filepath); + explicit CDxfWrite(const char* filepath); ~CDxfWrite(); void init(); @@ -309,25 +311,59 @@ public: class ImportExport CDxfRead { private: + // Low-level reader members std::ifstream* m_ifs; + int m_record_type = 0; + char m_record_data[1024] = ""; + bool m_not_eof = true; + int m_line = 0; + bool m_repeat_last_record = false; - bool m_fail; - char m_str[1024]; - char m_unused_line[1024]; - eDxfUnits_t m_eUnits; - bool m_measurement_inch; - char m_layer_name[1024]; - char m_section_name[1024]; - char m_block_name[1024]; - bool m_ignore_errors; + // file-level options/properties + eDxfUnits_t m_eUnits = eMillimeters; + bool m_measurement_inch = false; + // The following provide a state when reading any entity: If m_block_name is not empty the + // entity is in a BLOCK being defined, and LayerName() will return "BLOCKS xxxx" where xxxx is + // the block name. Otherwise m_layer_name will be the layer name from the entity records + // (default to "0") and LayerName() will return "ENTITIES xxxx" where xxxx is the layer name. + // This is clunky but it is a non-private interface and so difficult to change. + char m_layer_name[1024] = "0"; + char m_block_name[1024] = ""; + + + // Error-handling control + bool m_ignore_errors = true; + bool m_fail = false; std::map m_layer_ColorIndex_map; // Mapping from layer name -> layer color index const ColorIndex_t ColorBylayer = 256; + const ColorIndex_t ColorByBlock = 0; + + // Readers for various parts of the DXF file. + // Section readers (sections are identified by the type-2 (name) record they start with and each + // has its own reader function + bool ReadHeaderSection(); + bool ReadTablesSection(); + bool ReadBlocksSection(); + bool ReadEntitiesSection(); + + bool ReadIgnoredSection(); + + // The Tables section consists of several tables (again identified by their type-2 record asfter + // th 0-TABLE record) each with its own reader + bool ReadLayerTable(); + + // Some tables we ignore completely, using this method, which some of the above are + // inline-defined to. + bool ReadIgnoredTable(); bool ReadUnits(); bool ReadLayer(); + + bool ReadEntity(); // Identify entity type and read it + // Readers for specific entity types bool ReadLine(); bool ReadText(); bool ReadArc(); @@ -337,7 +373,13 @@ private: bool ReadSpline(); bool ReadLwPolyLine(); bool ReadPolyLine(); - bool ReadVertex(double* pVertex, bool* bulge_found, double* bulge); + typedef struct + { + double location[3]; + double bulge; + } VertexInfo; + + bool OnReadPolyline(std::list&, int flags); void OnReadArc(double start_angle, double end_angle, double radius, @@ -352,31 +394,79 @@ private: double end_angle); bool ReadInsert(); bool ReadDimension(); + bool ReadUnknownEntity(); + // Helper for reading common attributes for entities + void InitializeAttributes(); + void InitializeCommonEntityAttributes(); + void Setup3DVectorAttribute(int x_record_type, double destination[3]); + void SetupScaledDoubleAttribute(int record_type, double& destination); + void SetupScaledDoubleIntoList(int record_type, std::list& destination); + void Setup3DCoordinatesIntoLists(int x_record_type, + std::list& x_destination, + std::list& y_destination, + std::list& z_destination); + void SetupStringAttribute(int record_type, char* destination); + void SetupStringAttribute(int record_type, std::string& destination); + std::map> m_coordinate_attributes; + static void ProcessScaledDouble(CDxfRead* object, void* target); + static void ProcessScaledDoubleIntoList(CDxfRead* object, void* target); + static void ProcessString(CDxfRead* object, void* target); + static void ProcessStdString(CDxfRead* object, void* target); + // For all types T where strean >> x and x = 0 works + template + void SetupValueAttribute(int record_type, T& target); + // TODO: Once all compilers used for FreeCAD support class-level template specializations, + // SetupValueAttribute could have specializations and replace SetupStringAttribute etc. + // The template specialization is required to handle the (char *) case, which would + // otherwise try to read the actual pointer from the stream, or... what? + // The specialization would also handle the default value when it cannot be zero. + template + static void ProcessValue(CDxfRead* object, void* target); + + bool ProcessAttribute(); + void ProcessAllAttributes(); + bool ReadBlockInfo(); bool ReadVersion(); bool ReadDWGCodePage(); bool ResolveEncoding(); - void get_line(); - void put_line(const char* value); - void ResolveColorIndex(); + bool get_next_record(); + void repeat_last_record(); protected: - ColorIndex_t m_ColorIndex; - eDXFVersion_t m_version; // Version from $ACADVER variable in DXF - const char* (CDxfRead::*stringToUTF8)(const char*) const; + // common entity properties. Some properties are accumulated local to the reader method and + // passed to the ReadXxxx virtual method. Others are collected here as private values and also + // passed to ReadXxxx. Finally some of the attributes are accessed using references to + // public/protected fields or methods (such as LayerName()). Altogether a bit of a mishmash. + ColorIndex_t m_ColorIndex = 0; + char m_LineType[1024] = ""; + eDXFVersion_t m_version = RUnknown; // Version from $ACADVER variable in DXF + const char* (CDxfRead::*stringToUTF8)(const char*) const = &CDxfRead::UTF8ToUTF8; + // Although this is called "ImportWarning" it is just a wrapper to write a warning eithout any + // additional information such as a line number and as such, may be split into a basic + // message-writer and something that adds a line number. + template + void ImportError(const char* format, args... argValues) const + { + Base::ConsoleSingleton::Instance().Warning(format, argValues...); + } + void UnsupportedFeature(const char* format, ...); private: - const std::string* m_CodePage; // Code Page name from $DWGCODEPAGE or null if none/not read yet + std::map> m_unsupportedFeaturesNoted; + const std::string* m_CodePage = + nullptr; // 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. - const std::string* m_encoding; // A name for the encoding implied by m_version and m_CodePage + const std::string* m_encoding = + nullptr; // A name for the encoding implied by m_version and m_CodePage const char* UTF8ToUTF8(const char* encoded) const; const char* GeneralToUTF8(const char* encoded) const; public: - CDxfRead(const char* filepath); // this opens the file - virtual ~CDxfRead(); // this closes the file + explicit CDxfRead(const char* filepath); // this opens the file + virtual ~CDxfRead(); // this closes the file bool Failed() { @@ -433,6 +523,12 @@ public: virtual void AddGraphics() const {} + // These give the derived class access to common object properties std::string LayerName() const; + bool LineTypeIsHidden() const + { + return m_LineType[0] == 'h' || m_LineType[0] == 'H'; + } + App::Color ObjectColor() const; // as rgba value }; #endif diff --git a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp index 5f46012b23..a9888d115d 100644 --- a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp +++ b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp @@ -50,16 +50,20 @@ #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 "ImpExpDxfGui.h" @@ -67,5 +71,37 @@ using namespace ImportGui; ImpExpDxfReadGui::ImpExpDxfReadGui(std::string filepath, App::Document* pcDoc) : ImpExpDxfRead(filepath, pcDoc) + , GuiDocument(Gui::Application::Instance->getDocument(pcDoc)) {} +void ImpExpDxfReadGui::ApplyGuiStyles(Part::Feature* object) +{ + PartGui::ViewProviderPart* view = + static_cast(GuiDocument->getViewProvider(object)); + App::Color color = ObjectColor(); + view->LineColor.setValue(color); + view->PointColor.setValue(color); + view->ShapeColor.setValue(color); +} + +void ImpExpDxfReadGui::ApplyGuiStyles(App::FeaturePython* object) +{ + static Base::Type PropertyColorType = App::PropertyColor::getClassTypeId(); + + auto view = static_cast(GuiDocument->getViewProvider(object)); + App::Color color = ObjectColor(); + + // The properties on this object depend on which Python object is wrapped around it. + // For now we look for the two colors we expect in text and dimensions, and check that they + // exist and have the correct type before setting them. + // A more general choice would be to iterate over all the properties and set all the ones of + // this type, or perhaps only if their name ends in "Color" + auto prop = view->getPropertyByName("TextColor"); + if (prop != nullptr && prop->getTypeId() == PropertyColorType) { + static_cast(prop)->setValue(color); + } + prop = view->getPropertyByName("LineColor"); + if (prop != nullptr && prop->getTypeId() == PropertyColorType) { + static_cast(prop)->setValue(color); + } +} diff --git a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.h b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.h index 5b8808e723..24a1c9acea 100644 --- a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.h +++ b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -37,6 +38,11 @@ class ImpExpDxfReadGui: public Import::ImpExpDxfRead { public: ImpExpDxfReadGui(std::string filepath, App::Document* pcDoc); + +protected: + void ApplyGuiStyles(Part::Feature*); + void ApplyGuiStyles(App::FeaturePython*); + Gui::Document* GuiDocument; }; } // namespace ImportGui