diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.cpp b/src/Mod/Import/App/dxf/ImpExpDxf.cpp index 9ade2e2d2e..dbe6882acc 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.cpp +++ b/src/Mod/Import/App/dxf/ImpExpDxf.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -81,20 +82,128 @@ using BRepAdaptor_HCurve = BRepAdaptor_Curve; ImpExpDxfRead::ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc) : CDxfRead(filepath) , document(pcDoc) - , optionGroupLayers(false) - , optionImportAnnotations(true) { setOptionSource("User parameter:BaseApp/Preferences/Mod/Draft"); setOptions(); } +bool ImpExpDxfRead::ReadEntitiesSection() +{ + DrawingEntityCollector collector(*this); + if (m_mergeOption < SingleShapes) { + std::map> ShapesToCombine; + { + ShapeSavingEntityCollector savingCollector(*this, ShapesToCombine); + if (!CDxfRead::ReadEntitiesSection()) { + return false; + } + } + + // Merge the contents of ShapesToCombine and AddObject the result(s) + // TODO: We do end-to-end joining or complete merging as selected by the options. + for (auto& shapeSet : ShapesToCombine) { + m_entityAttributes = shapeSet.first; + CombineShapes(shapeSet.second, "Compound"); + } + } + else { + if (!CDxfRead::ReadEntitiesSection()) { + return false; + } + } + if (m_preserveLayers) { + // Hide the Hidden layers if possible (if GUI exists) + // We do this now rather than when the layer is created so all objects + // within the layers also become hidden. + for (auto& layerEntry : Layers) { + auto layer = (Layer*)layerEntry.second; + if (layer->DraftLayerView != nullptr && layer->Hidden) { + PyObject_CallMethod(layer->DraftLayerView, "hide", nullptr); + } + } + } + return true; +} + +void ImpExpDxfRead::CombineShapes(std::list& shapes, const char* nameBase) const +{ + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for (const auto& sh : shapes) { + if (!sh.IsNull()) { + builder.Add(comp, sh); + } + } + if (!comp.IsNull()) { + Collector->AddObject(comp, nameBase); + } +} + void ImpExpDxfRead::setOptions() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(getOptionSource().c_str()); - optionGroupLayers = hGrp->GetBool("groupLayers", false); - optionImportAnnotations = hGrp->GetBool("dxftext", false); + m_preserveLayers = hGrp->GetBool("dxfUseDraftVisGroups", true); + m_preserveColors = hGrp->GetBool("dxfGetOriginalColors", true); + // 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; + if (hGrp->GetBool("groupLayers", true)) { + // Group all compatible objects together + m_mergeOption = MergeShapes; + } + else if (hGrp->GetBool("dxfCreatePart", true)) { + // Create (non-draft) Shape objects when possible + m_mergeOption = SingleShapes; + } + else if (hGrp->GetBool("dxfCreateDraft", true)) { + // Create only Draft objects, making the result closest to drawn-from-scratch + m_mergeOption = DraftObjects; + } + // TODO: joingeometry should give an intermediate between MergeShapes and SingleShapes which + // will merge shapes that happen to join end-to-end. As such it should be in the radio button + // set, except that the legacy importer can do joining either for sketches or for shapes. What + // this really means is there should be an "Import as sketch" checkbox, and only the + // MergeShapes, JoinShapes, and SingleShapes radio buttons should be allowed, i.e. Draft Objects + // would be ignored. SetAdditionalScaling(hGrp->GetFloat("dxfScaling", 1.0)); + + m_importAnnotations = hGrp->GetBool("dxftext", false); + m_importPoints = hGrp->GetBool("dxfImportPoints", true); + m_importPaperSpaceEntities = hGrp->GetBool("dxflayout", false); + m_importHiddenBlocks = hGrp->GetBool("dxfstarblocks", false); + // TODO: There is currently no option for this: m_importFrozenLayers = + // hGrp->GetBool("dxffrozenLayers", false); + // TODO: There is currently no option for this: m_importHiddenLayers = + // hGrp->GetBool("dxfhiddenLayers", true); +} + +bool ImpExpDxfRead::OnReadBlock(const std::string& name, int flags) +{ + if ((flags & 0x04) != 0) { + // 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"); + } + else if (!m_importHiddenBlocks && (flags & 0x01) != 0) { + // It is an anonymous block used to build dimensions, hatches, etc so we don't need it + // and don't want to be complaining about unhandled entity types. + // Note that if it *is* for a hatch we could actually import it and use it to draw a hatch. + } + else if (Blocks.count(name) > 0) { + ImportError("Duplicate block name '%s'\n", name); + } + else { + Block& block = Blocks.insert(std::make_pair(name, Block(name, flags))).first->second; + BlockDefinitionCollector blockCollector(*this, + block.Shapes, + block.FeatureBuildersList, + block.Inserts); + return ReadBlockContents(); + } + return SkipBlockContents(); } void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start, @@ -107,17 +216,13 @@ void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start, // TODO: Really?? What about the people designing integrated circuits? return; } - BRepBuilderAPI_MakeEdge makeEdge(p0, p1); - TopoDS_Edge edge = makeEdge.Edge(); - AddObject(new Part::TopoShape(edge)); + Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line"); } void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start) { - BRepBuilderAPI_MakeVertex makeVertex(makePoint(start)); - TopoDS_Vertex vertex = makeVertex.Vertex(); - AddObject(new Part::TopoShape(vertex)); + Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point"); } @@ -136,9 +241,7 @@ void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start, gp_Pnt pc = makePoint(center); gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); if (circle.Radius() > 0) { - BRepBuilderAPI_MakeEdge makeEdge(circle, p0, p1); - TopoDS_Edge edge = makeEdge.Edge(); - AddObject(new Part::TopoShape(edge)); + Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc"); } else { Base::Console().Warning("ImpExpDxf - ignore degenerate arc of circle\n"); @@ -159,9 +262,7 @@ void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start, gp_Pnt pc = makePoint(center); gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); if (circle.Radius() > 0) { - BRepBuilderAPI_MakeEdge makeEdge(circle); - TopoDS_Edge edge = makeEdge.Edge(); - AddObject(new Part::TopoShape(edge)); + Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle"); } else { Base::Console().Warning("ImpExpDxf - ignore degenerate circle\n"); @@ -278,9 +379,7 @@ void ImpExpDxfRead::OnReadSpline(struct SplineData& sd) throw Standard_Failure(); } - BRepBuilderAPI_MakeEdge makeEdge(geom); - TopoDS_Edge edge = makeEdge.Edge(); - AddObject(new Part::TopoShape(edge)); + Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline"); } catch (const Standard_Failure&) { Base::Console().Warning("ImpExpDxf - failed to create bspline\n"); @@ -305,9 +404,7 @@ void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center, gp_Elips ellipse(gp_Ax2(pc, up), major_radius, minor_radius); ellipse.Rotate(gp_Ax1(pc, up), rotation); if (ellipse.MinorRadius() > 0) { - BRepBuilderAPI_MakeEdge makeEdge(ellipse); - TopoDS_Edge edge = makeEdge.Edge(); - AddObject(new Part::TopoShape(edge)); + Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse"); } else { Base::Console().Warning("ImpExpDxf - ignore degenerate ellipse\n"); @@ -322,13 +419,15 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, { // Note that our parameters do not contain all the information needed to properly orient the // text. As a result the text will always appear on the XY plane - if (optionImportAnnotations) { - if (LayerName().rfind("BLOCKS", 0) != 0) { - PyObject* draftModule = nullptr; - Base::Rotation rot(Base::Vector3d(0, 0, 1), rotation); - PyObject* placement = new Base::PlacementPy(Base::Placement(point, rot)); - draftModule = PyImport_ImportModule("Draft"); + if (m_importAnnotations) { + auto makeText = [=](const Base::Matrix4D& transform) -> App::FeaturePython* { + PyObject* draftModule = getDraftModule(); if (draftModule != nullptr) { + Base::Matrix4D localTransform; + localTransform.rotZ(rotation); + localTransform.move(point); + PyObject* placement = + new Base::PlacementPy(Base::Placement(transform * localTransform)); // returns a wrapped App::FeaturePython auto builtText = dynamic_cast*>( // NOLINTNEXTLINE(readability/nolint) @@ -340,18 +439,14 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, placement, 0, height)); + Py_DECREF(placement); if (builtText != nullptr) { - ApplyGuiStyles( - dynamic_cast(builtText->getDocumentObjectPtr())); + return dynamic_cast(builtText->getDocumentObjectPtr()); } } - // We own all the return values so we must release them. - Py_DECREF(placement); - Py_XDECREF(draftModule); - } - else { - UnsupportedFeature("TEXT or MTEXT in BLOCK definition"); - } + return nullptr; + }; + Collector->AddObject((FeaturePythonBuilder)makeText); } } @@ -361,37 +456,78 @@ void ImpExpDxfRead::OnReadInsert(const Base::Vector3d& point, const std::string& name, double rotation) { - // std::cout << "Inserting block " << name << " rotation " << rotation << " pos " << point[0] << - // "," << point[1] << "," << point[2] << " scale " << scale[0] << "," << scale[1] << "," << - // scale[2] << std::endl; - std::string prefix = "BLOCKS "; - prefix += name; - prefix += " "; - auto checkScale = [=](double scale) { - return scale != 0.0 ? scale : 1.0; - }; - for (const auto& layer : layers) { - if (layer.first.substr(0, prefix.size()) == prefix) { - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); - for (const auto& shape : layer.second) { - const TopoDS_Shape& sh = shape->getShape(); - if (!sh.IsNull()) { - builder.Add(comp, sh); - } - } - if (!comp.IsNull()) { - auto pcomp = new Part::TopoShape(comp); - Base::Matrix4D mat; - mat.scale(checkScale(scale[0]), checkScale(scale[1]), checkScale(scale[2])); - mat.rotZ(rotation); - mat.move(point[0], point[1], point[2]); - pcomp->transformShape(mat, true); - AddObject(pcomp); + Collector->AddInsert(point, scale, name, rotation); +} +void ImpExpDxfRead::ExpandInsert(const std::string& name, + const Base::Matrix4D& transform, + const Base::Vector3d& point, + double rotation, + const Base::Vector3d& scale) +{ + if (Blocks.count(name) == 0) { + ImportError("Reference to undefined or external block '%s'\n", name); + return; + } + Block& block = Blocks.at(name); + // Apply the scaling, rotation, and move before the OCSEnttityTransform and place the result io + // BaseEntityTransform, + Base::Matrix4D localTransform; + localTransform.scale(scale.x, scale.y, scale.z); + localTransform.rotZ(rotation); + localTransform.move(point[0], point[1], point[2]); + localTransform = transform * localTransform; + CommonEntityAttributes mainAttributes = m_entityAttributes; + for (const auto& [attributes, shapes] : block.Shapes) { + // Put attributes into m_entityAttributes after using the latter to set byblock values in + // the former. + m_entityAttributes = attributes; + m_entityAttributes.ResolveByBlockAttributes(mainAttributes); + + for (const TopoDS_Shape& shape : shapes) { + // TODO???: See the comment in TopoShape::makeTransform regarding calling + // Moved(identityTransform) on the new shape + Collector->AddObject( + BRepBuilderAPI_Transform(shape, + Part::TopoShape::convert(localTransform), + Standard_True) + .Shape(), + "InsertPart"); // TODO: The collection should contain the nameBase to use + } + } + for (const auto& [attributes, featureBuilders] : block.FeatureBuildersList) { + // Put attributes into m_entityAttributes after using the latter to set byblock values in + // the former. + m_entityAttributes = attributes; + m_entityAttributes.ResolveByBlockAttributes(mainAttributes); + + for (const FeaturePythonBuilder& featureBuilder : featureBuilders) { + // TODO: Any non-identity transform from the original entity record needs to be applied + // before OCSEntityTransform (which includes this INSERT's transform followed by the + // transform for the INSERT's context (i.e. from an outeer INSERT) + // TODO: Perhaps pass a prefix ("Insert") to the builder to make the object name so + // Draft objects in a block get named similarly to Shapes. + App::FeaturePython* feature = featureBuilder(localTransform); + if (feature != nullptr) { + // Note that the featureBuilder has already placed this object in the drawing as a + // top-level object, so we don't have to add them but we must place it in its layer + // and set its gui styles + MoveToLayer(feature); + ApplyGuiStyles(feature); } } } + for (const auto& [attributes, inserts] : block.Inserts) { + // Put attributes into m_entityAttributes after using the latter to set byblock values in + // the former. + m_entityAttributes = attributes; + m_entityAttributes.ResolveByBlockAttributes(mainAttributes); + + for (const Block::Insert& insert : inserts) { + // TODO: Apply the OCSOrientationTransform saved with the Insert statement to + // localTransform. (pass localTransform*insert.OCSDirectionTransform) + ExpandInsert(insert.Name, localTransform, insert.Point, insert.Rotation, insert.Scale); + } + } } @@ -400,53 +536,160 @@ void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start, const Base::Vector3d& point, double /*rotation*/) { - if (optionImportAnnotations) { - PyObject* draftModule = nullptr; - PyObject* startPy = new Base::VectorPy(start); - PyObject* endPy = new Base::VectorPy(end); - PyObject* lineLocationPy = new Base::VectorPy(point); - draftModule = PyImport_ImportModule("Draft"); - if (draftModule != nullptr) { - // returns a wrapped App::FeaturePython - auto builtDim = dynamic_cast*>( - // NOLINTNEXTLINE(readability/nolint) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - (Base::PyObjectBase*)PyObject_CallMethod(draftModule, - "make_linear_dimension", - "OOO", - startPy, - endPy, - lineLocationPy)); - if (builtDim != nullptr) { - ApplyGuiStyles(dynamic_cast(builtDim->getDocumentObjectPtr())); + if (m_importAnnotations) { + auto makeDimension = [=](const Base::Matrix4D& transform) -> App::FeaturePython* { + PyObject* draftModule = getDraftModule(); + if (draftModule != nullptr) { + // TODO: Capture and apply OCSOrientationTransform to OCS coordinates + // Note, some of the locations in the DXF are OCS and some are UCS, but UCS doesn't + // mean UCS when in a block expansion, it means 'transform' + // So we want transform*vector for "UCS" coordinates and transform*ocdCapture*vector + // for "OCS" coordinates + // + // We implement the transform by mapping all the points from OCS to UCS + // TODO: Set the Normal property to transform*(0,0,1,0) + // TODO: Set the Direction property to transform*(the desired direction). + // By default this is parallel to (start-end). + PyObject* startPy = new Base::VectorPy(transform * start); + PyObject* endPy = new Base::VectorPy(transform * end); + PyObject* lineLocationPy = new Base::VectorPy(transform * point); + // returns a wrapped App::FeaturePython + auto builtDim = dynamic_cast*>( + // NOLINTNEXTLINE(readability/nolint) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + (Base::PyObjectBase*)PyObject_CallMethod(draftModule, + "make_linear_dimension", + "OOO", + startPy, + endPy, + lineLocationPy)); + Py_DECREF(startPy); + Py_DECREF(endPy); + Py_DECREF(lineLocationPy); + if (builtDim != nullptr) { + return dynamic_cast(builtDim->getDocumentObjectPtr()); + } } - } - // We own all the return values so we must release them. - Py_DECREF(startPy); - Py_DECREF(endPy); - Py_DECREF(lineLocationPy); - Py_XDECREF(draftModule); + return nullptr; + }; + Collector->AddObject((FeaturePythonBuilder)makeDimension); + } +} +void ImpExpDxfRead::OnReadPolyline(std::list& vertices, int flags) +{ + std::map> ShapesToCombine; + { + // TODO: Currently ExpandPolyline calls OnReadArc etc to generate the pieces, and these + // create TopoShape objects which ShapeSavingEntityCollector can gather up. + // Eventually when m_mergeOption being DraftObjects is implemented OnReadArc etc might + // generate Draft objects which ShapeSavingEntityCollector does not save. + // We need either a collector that collects everything (and we have to figure out + // how to join Draft objects) or we need to temporarily set m_mergeOption to SingleShapes + // if it is set to DraftObjects (and safely restore it on exceptions) + // A clean way would be to give the collector a "makeDraftObjects" property, + // and our special collector could give this the value 'false' whereas the main + // collector would base this on the option setting. + // Also ShapeSavingEntityCollector classifies by entityAttributes which is not needed here + // because they are constant throughout. + ShapeSavingEntityCollector savingCollector(*this, ShapesToCombine); + ExplodePolyline(vertices, flags); + } + // Join the shapes. + if (!ShapesToCombine.empty()) { + // TODO: If we want Draft objects and all segments are straight lines we can make a draft + // wire. + CombineShapes(ShapesToCombine.begin()->second, "Polyline"); } } -void ImpExpDxfRead::AddObject(Part::TopoShape* shape) +ImpExpDxfRead::Layer::Layer(const std::string& name, + ColorIndex_t color, + std::string&& lineType, + PyObject* drawingLayer) + : CDxfRead::Layer(name, color, std::move(lineType)) + , DraftLayer(drawingLayer) + , DraftLayerView(drawingLayer == nullptr ? nullptr + : PyObject_GetAttrString(drawingLayer, "ViewObject")) +{} +ImpExpDxfRead::Layer::~Layer() { - std::vector vec; - std::string destinationLayerName(LayerName()); - if (layers.count(destinationLayerName) != 0) { - vec = layers[destinationLayerName]; + Py_XDECREF(DraftLayer); + Py_XDECREF(DraftLayerView); +} + +CDxfRead::Layer* +ImpExpDxfRead::MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType) +{ + if (m_preserveLayers) { + // Hidden layers are implemented in the wrapup code after the entire file has been read. + App::Color appColor = ObjectColor(color); + PyObject* draftModule = nullptr; + PyObject* layer = nullptr; + draftModule = PyImport_ImportModule("Draft"); + if (draftModule != nullptr) { + // After the colours, I also want to pass the draw_style, but there is an intervening + // line-width parameter. It is easier to just pass that parameter's default value than + // to do the handstands to pass a named parameter. + // TODO: Pass the appropriate draw_style (from "Solid" "Dashed" "Dotted" "DashDot") + // This needs an ObjectDrawStyleName analogous to ObjectColor but at the ImpExpDxfGui + // level. + layer = + // NOLINTNEXTLINE(readability/nolint) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + (Base::PyObjectBase*)PyObject_CallMethod(draftModule, + "make_layer", + "s(fff)(fff)fs", + name.c_str(), + appColor.r, + appColor.g, + appColor.b, + appColor.r, + appColor.g, + appColor.b, + 2.0, + "Solid"); + Py_DECREF(draftModule); + } + auto result = new Layer(name, color, std::move(lineType), layer); + if (result->DraftLayerView != nullptr) { + PyObject_SetAttrString(result->DraftLayerView, "OverrideLineColorChildren", Py_False); + PyObject_SetAttrString(result->DraftLayerView, "OverrideShapeColorChildren", Py_False); + } + + // We make our own layer class even if we could not make a layer. MoveToLayer will ignore + // such layers but we have to do this because it is not a polymorphic type so we can't tell + // what we pull out of m_entityAttributes.m_Layer. + return result; } - vec.push_back(shape); - layers[destinationLayerName] = vec; - if (!optionGroupLayers) { - if (destinationLayerName.rfind("BLOCKS", 0) != 0) { - auto pcFeature = - dynamic_cast(document->addObject("Part::Feature", "Shape")); - pcFeature->Shape.setValue(shape->getShape()); - ApplyGuiStyles(pcFeature); + return CDxfRead::MakeLayer(name, color, std::move(lineType)); +} +void ImpExpDxfRead::MoveToLayer(App::DocumentObject* object) const +{ + if (m_preserveLayers) { + static PyObject* addObjectName = + PyUnicode_FromString("addObject"); // This never gets freed, we always have a reference + auto layer = static_cast(m_entityAttributes.m_Layer); + if (layer->DraftLayer != nullptr) { + // We have to move the object to layer->DraftLayer + // The DraftLayer will have a Proxy attribute which has a addObject attribute which we + // call with (draftLayer, draftObject) Checking from python, the layer is a + // App::FeaturePython, and its Proxy is a draftobjects.layer.Layer + PyObject* proxy = PyObject_GetAttrString(layer->DraftLayer, "Proxy"); + if (proxy != nullptr) { + // TODO: De we have to check if the method exists? The legacy importer does. + PyObject_CallMethodObjArgs(proxy, + addObjectName, + layer->DraftLayer, + object->getPyObject(), + nullptr); + Py_DECREF(proxy); + return; + } } } + // TODO: else Hide the object if it is in a Hidden layer? That won't work because we've cleared + // out m_entityAttributes.m_Layer } @@ -487,32 +730,21 @@ std::string ImpExpDxfRead::Deformat(const char* text) return ss.str(); } - -void ImpExpDxfRead::AddGraphics() const +void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape, + const char* nameBase) { - if (optionGroupLayers) { - for (const auto& layer : layers) { - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); - std::string k = layer.first; - if (k == "0") { // FreeCAD doesn't like an object name being '0'... - k = "LAYER_0"; - } - if (k.rfind("BLOCKS", 0) != 0) { - for (const auto& shape : layer.second) { - const TopoDS_Shape& sh = shape->getShape(); - if (!sh.IsNull()) { - builder.Add(comp, sh); - } - } - if (!comp.IsNull()) { - auto pcFeature = dynamic_cast( - document->addObject("Part::Feature", k.c_str())); - pcFeature->Shape.setValue(comp); - } - } - } + auto pcFeature = + dynamic_cast(Reader.document->addObject("Part::Feature", nameBase)); + pcFeature->Shape.setValue(shape); + Reader.MoveToLayer(pcFeature); + Reader.ApplyGuiStyles(pcFeature); +} +void ImpExpDxfRead::DrawingEntityCollector::AddObject(FeaturePythonBuilder shapeBuilder) +{ + App::FeaturePython* shape = shapeBuilder(Reader.OCSOrientationTransform); + if (shape != nullptr) { + Reader.MoveToLayer(shape); + Reader.ApplyGuiStyles(shape); } } diff --git a/src/Mod/Import/App/dxf/ImpExpDxf.h b/src/Mod/Import/App/dxf/ImpExpDxf.h index 349c0d77c7..d219e0f99f 100644 --- a/src/Mod/Import/App/dxf/ImpExpDxf.h +++ b/src/Mod/Import/App/dxf/ImpExpDxf.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -40,8 +41,19 @@ class ImportExport ImpExpDxfRead: public CDxfRead { public: ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc); + ImpExpDxfRead(const ImpExpDxfRead&) = delete; + ImpExpDxfRead(ImpExpDxfRead&&) = delete; + void operator=(const ImpExpDxfRead&) = delete; + void operator=(ImpExpDxfRead&&) = delete; + ~ImpExpDxfRead() override + { + Py_XDECREF(DraftModule); + } + + bool ReadEntitiesSection() override; // CDxfRead's virtual functions + bool OnReadBlock(const std::string& name, int flags) override; void OnReadLine(const Base::Vector3d& start, const Base::Vector3d& end, bool hidden) override; void OnReadPoint(const Base::Vector3d& start) override; void OnReadText(const Base::Vector3d& point, @@ -69,14 +81,20 @@ public: const Base::Vector3d& scale, const std::string& name, double rotation) override; + // Expand a block reference; this should only happen when the collector draws to the document + // rather than saving things The transform should include the OCS Orientation transform for the + // insertion. + void ExpandInsert(const std::string& name, + const Base::Matrix4D& transform, + const Base::Vector3d& point, + double rotation, + const Base::Vector3d& scale); void OnReadDimension(const Base::Vector3d& start, const Base::Vector3d& end, const Base::Vector3d& point, double rotation) override; - void AddGraphics() const override; + void OnReadPolyline(std::list& /*vertices*/, int flags) override; - // FreeCAD-specific functions - void AddObject(Part::TopoShape* shape); // Called by OnRead functions to add Part objects std::string Deformat(const char* text); // Removes DXF formatting from texts std::string getOptionSource() @@ -94,17 +112,235 @@ private: { return {point3d.x, point3d.y, point3d.z}; } + void MoveToLayer(App::DocumentObject* object) const; + // Combine all the shapes in the given shapes collection into a single shape, and AddObject that + // to the drawing. unref's all the shapes in the collection, possibly freeing them. + void CombineShapes(std::list& shapes, const char* nameBase) const; + PyObject* DraftModule = nullptr; protected: - virtual void ApplyGuiStyles(Part::Feature* /*object*/) - {} - virtual void ApplyGuiStyles(App::FeaturePython* /*object*/) - {} + PyObject* getDraftModule() + { + if (DraftModule == nullptr) { + DraftModule = PyImport_ImportModule("Draft"); + } + return DraftModule; + } + CDxfRead::Layer* + MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType) override; + + // Overrides for layer management so we can record the layer objects in the FreeCAD drawing that + // are associated with the layers in the DXF. + class Layer: public CDxfRead::Layer + { + public: + Layer(const std::string& name, + ColorIndex_t color, + std::string&& lineType, + PyObject* drawingLayer); + Layer(const Layer&) = delete; + Layer(Layer&&) = delete; + void operator=(const Layer&) = delete; + void operator=(Layer&&) = delete; + ~Layer() override; + PyObject* const DraftLayer; + PyObject* const DraftLayerView; + }; + + using FeaturePythonBuilder = + std::function; + // Block management + class Block + { + public: + struct Insert + { + const Base::Vector3d Point; + const Base::Vector3d Scale; + const std::string Name; + const double Rotation; + + // NOLINTNEXTLINE(readability/nolint) + // NOLINTNEXTLINE(modernize-pass-by-value) Pass by value adds unwarranted complexity + Insert(const std::string& Name, + const Base::Vector3d& Point, + double Rotation, + const Base::Vector3d& Scale) + : Point(Point) + , Scale(Scale) + , Name(Name) + , Rotation(Rotation) + {} + }; + // NOLINTNEXTLINE(readability/nolint) + // NOLINTNEXTLINE(modernize-pass-by-value) Pass by value adds unwarranted complexity + Block(const std::string& name, int flags) + : Name(name) + , Flags(flags) + {} + const std::string Name; + const int Flags; + std::map> Shapes; + std::map> + FeatureBuildersList; + std::map> Inserts; + }; + +private: + std::map Blocks; App::Document* document; - bool optionGroupLayers; - bool optionImportAnnotations; - std::map> layers; std::string m_optionSource; + +protected: + virtual void ApplyGuiStyles(Part::Feature* /*object*/) const + {} + virtual void ApplyGuiStyles(App::FeaturePython* /*object*/) const + {} + + // Gathering of created entities + class EntityCollector + { + public: + explicit EntityCollector(ImpExpDxfRead& reader) + : Reader(reader) + , previousCollector(reader.Collector) + { + Reader.Collector = this; + } + EntityCollector(const EntityCollector&) = delete; + EntityCollector(EntityCollector&&) = delete; + void operator=(const EntityCollector&) = delete; + void operator=(EntityCollector&&) = delete; + + virtual ~EntityCollector() + { + if (Reader.Collector == this) { + Reader.Collector = previousCollector; + } + } + + // Called by OnReadXxxx functions to add Part objects + virtual void AddObject(const TopoDS_Shape& shape, const char* nameBase) = 0; + // Called by OnReadXxxx functions to add FeaturePython (draft) objects. + // Because we can't readily copy Draft objects, this method instead takes a builder which, + // when called, creates and returns the object. + virtual void AddObject(FeaturePythonBuilder shapeBuilder) = 0; + // Called by OnReadInsert to either remember in a nested block or expand the block into the + // drawing + virtual void AddInsert(const Base::Vector3d& point, + const Base::Vector3d& scale, + const std::string& name, + double rotation) = 0; + + protected: + ImpExpDxfRead& Reader; + + private: + EntityCollector* const previousCollector; + }; + class DrawingEntityCollector: public EntityCollector + { + // This collector places all objects into the drawing + public: + explicit DrawingEntityCollector(ImpExpDxfRead& reader) + : EntityCollector(reader) + {} + + void AddObject(const TopoDS_Shape& shape, const char* nameBase) override; + void AddObject(FeaturePythonBuilder shapeBuilder) override; + void AddInsert(const Base::Vector3d& point, + const Base::Vector3d& scale, + const std::string& name, + double rotation) override + { + Reader.ExpandInsert(name, Reader.OCSOrientationTransform, point, rotation, scale); + } + }; + class ShapeSavingEntityCollector: public DrawingEntityCollector + { + // This places draft objects into the drawing but stashes away Shapes. + public: + ShapeSavingEntityCollector( + ImpExpDxfRead& reader, + std::map>& shapesList) + : DrawingEntityCollector(reader) + , ShapesList(shapesList) + {} + + void AddObject(const TopoDS_Shape& shape, const char* /*nameBase*/) override + { + ShapesList[Reader.m_entityAttributes].push_back(shape); + } + + private: + std::map>& ShapesList; + }; +#ifdef LATER + class PolylineEntityCollector: public CombiningDrawingEntityCollector + { + // This places draft objects into the drawing but stashes away Shapes. + public: + explicit PolylineEntityCollector(CDxfRead& reader) + : CombiningDrawingEntityCollector(reader) + , previousMmergeOption(reader.m_mergeOption) + { + // TODO: We also have to temporarily shift from DraftObjects to some other mode so the + // pieces of the polyline some through as shapes and not Draft objects, or maybe the + // polyline builder should not call OnArcRead and OnLineRead to do their dirty work. + } + ~PolylineEntityCollector(); + + void AddObject(Part::TopoShape* shape) override; + void AddObject(App::FeaturePython* shape) override; + + private: + const EntityCollector* previousEntityCollector; + const eEntityMergeType_t previousMmergeOption; + }; +#endif + class BlockDefinitionCollector: public EntityCollector + { + // Collect all the entities plus their entityAttrubutes into given collections. + public: + BlockDefinitionCollector( + ImpExpDxfRead& reader, + std::map>& shapesList, + std::map>& + featureBuildersList, + std::map>& insertsList) + : EntityCollector(reader) + , ShapesList(shapesList) + , FeatureBuildersList(featureBuildersList) + , InsertsList(insertsList) + {} + + // TODO: We will want AddAttributeDefinition as well. + void AddObject(const TopoDS_Shape& shape, const char* /*nameBase*/) override + { + ShapesList[Reader.m_entityAttributes].push_back(shape); + } + void AddObject(FeaturePythonBuilder shapeBuilder) override + { + FeatureBuildersList[Reader.m_entityAttributes].push_back(shapeBuilder); + } + void AddInsert(const Base::Vector3d& point, + const Base::Vector3d& scale, + const std::string& name, + double rotation) override + { + InsertsList[Reader.m_entityAttributes].push_back( + Block::Insert(name, point, rotation, scale)); + } + + private: + std::map>& ShapesList; + std::map>& + FeatureBuildersList; + std::map>& InsertsList; + }; + +private: + EntityCollector* Collector = nullptr; }; class ImportExport ImpExpDxfWrite: public CDxfWrite @@ -122,7 +358,7 @@ public: { return m_optionSource; } - void setOptionSource(std::string s) + void setOptionSource(const std::string& s) { m_optionSource = s; } diff --git a/src/Mod/Import/App/dxf/dxf.cpp b/src/Mod/Import/App/dxf/dxf.cpp index 5d9920620a..7597adef98 100644 --- a/src/Mod/Import/App/dxf/dxf.cpp +++ b/src/Mod/Import/App/dxf/dxf.cpp @@ -1,4 +1,4 @@ -// dxf.cpp +// dxf.cpp // Copyright (c) 2009, Dan Heeks // This program is released under the BSD license. See the file COPYING for details. // modified 2018 wandererfan @@ -1813,8 +1813,16 @@ CDxfRead::CDxfRead(const std::string& filepath) CDxfRead::~CDxfRead() { delete m_ifs; + // Delete the Layer objects which are referenced by pointer from the Layers table. + for (auto& pair : Layers) { + delete pair.second; + } } +// Static member initializers +const std::string CDxfRead::LineTypeByLayer("BYLAYER"); // NOLINT(runtime/string) +const std::string CDxfRead::LineTypeByBlock("BYBLOCK"); // NOLINT(runtime/string) +const std::string CDxfRead::DefaultLineType("CONTINUOUS"); // NOLINT(runtime/string) // // Setup for ProcessCommonEntityAttribute @@ -1842,6 +1850,12 @@ void CDxfRead::SetupScaledDoubleIntoList(eDXFGroupCode_t x_record_type, list