/*************************************************************************** * Copyright (c) 2015 Yorik van Havre (yorik@uncreated.net) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #if OCC_VERSION_HEX < 0x070600 #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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ImpExpDxf.h" using namespace Import; #if OCC_VERSION_HEX >= 0x070600 using BRepAdaptor_HCurve = BRepAdaptor_Curve; #endif //****************************************************************************** // reading ImpExpDxfRead::ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc) : CDxfRead(filepath) , document(pcDoc) { 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, m_entityAttributes.m_Layer == nullptr ? "Compound" : m_entityAttributes.m_Layer->Name.c_str()); } } else { if (!CDxfRead::ReadEntitiesSection()) { return false; } } if (m_preserveLayers) { for (auto& layerEntry : Layers) { ((Layer*)layerEntry.second)->FinishLayer(); } } 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()); m_stats.importSettings.clear(); m_preserveLayers = hGrp->GetBool("dxfUseDraftVisGroups", true); m_stats.importSettings["Use layers"] = m_preserveLayers ? "Yes" : "No"; m_preserveColors = hGrp->GetBool("dxfGetOriginalColors", true); m_stats.importSettings["Use colors from the DXF file"] = m_preserveColors ? "Yes" : "No"; // Default for creation type is to create draft objects. // The radio-button structure of the options dialog should generally prevent this condition. m_mergeOption = DraftObjects; m_stats.importSettings["Merge option"] = "Create Draft objects"; // Default if (hGrp->GetBool("groupLayers", true)) { // Group all compatible objects together m_mergeOption = MergeShapes; m_stats.importSettings["Merge option"] = "Group layers into blocks"; } else if (hGrp->GetBool("dxfCreatePart", true)) { // Create (non-draft) Shape objects when possible m_mergeOption = SingleShapes; m_stats.importSettings["Merge option"] = "Create Part shapes"; } else if (hGrp->GetBool("dxfCreateDraft", true)) { // Create only Draft objects, making the result closest to drawn-from-scratch m_mergeOption = DraftObjects; m_stats.importSettings["Merge option"] = "Create Draft objects"; } // TODO: joingeometry should give an intermediate between MergeShapes and SingleShapes which // will merge shapes that happen to join end-to-end. As such it should be in the radio button // 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. bool joinGeometry = hGrp->GetBool("joingeometry", false); m_stats.importSettings["Join geometry"] = joinGeometry ? "Yes" : "No"; double scaling = hGrp->GetFloat("dxfScaling", 1.0); SetAdditionalScaling(scaling); m_stats.importSettings["Manual scaling factor"] = std::to_string(scaling); m_importAnnotations = hGrp->GetBool("dxftext", false); m_stats.importSettings["Import texts and dimensions"] = m_importAnnotations ? "Yes" : "No"; m_importPoints = hGrp->GetBool("dxfImportPoints", true); m_stats.importSettings["Import points"] = m_importPoints ? "Yes" : "No"; m_importPaperSpaceEntities = hGrp->GetBool("dxflayout", false); m_stats.importSettings["Import layout objects"] = m_importPaperSpaceEntities ? "Yes" : "No"; m_importHiddenBlocks = hGrp->GetBool("dxfstarblocks", false); m_stats.importSettings["Import hidden blocks"] = m_importHiddenBlocks ? "Yes" : "No"; // TODO: There is currently no option for this: m_importFrozenLayers = // hGrp->GetBool("dxffrozenLayers", false); // TODO: There is currently no option for this: m_importHiddenLayers = // 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.contains(name)) { 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, const Base::Vector3d& end, bool /*hidden*/) { if (shouldSkipEntity()) { return; } gp_Pnt p0 = makePoint(start); gp_Pnt p1 = makePoint(end); if (p0.IsEqual(p1, 0.00000001)) { // TODO: Really?? What about the people designing integrated circuits? return; } Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line"); } void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start) { if (shouldSkipEntity()) { return; } Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point"); } void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start, const Base::Vector3d& end, const Base::Vector3d& center, bool dir, bool /*hidden*/) { if (shouldSkipEntity()) { return; } gp_Pnt p0 = makePoint(start); gp_Pnt p1 = makePoint(end); gp_Dir up(0, 0, 1); if (!dir) { up = -up; } gp_Pnt pc = makePoint(center); gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); if (circle.Radius() > 0) { Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc"); } else { Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n"); } } void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start, const Base::Vector3d& center, bool dir, bool /*hidden*/) { if (shouldSkipEntity()) { return; } gp_Pnt p0 = makePoint(start); gp_Dir up(0, 0, 1); if (!dir) { up = -up; } gp_Pnt pc = makePoint(center); gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); if (circle.Radius() > 0) { Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle"); } else { Base::Console().warning("ImpExpDxf - ignore degenerate circle\n"); } } Handle(Geom_BSplineCurve) getSplineFromPolesAndKnots(struct SplineData& sd) { std::size_t numPoles = sd.control_points; if (sd.controlx.size() > numPoles || sd.controly.size() > numPoles || sd.controlz.size() > numPoles || sd.weight.size() > numPoles) { return nullptr; } // handle the poles TColgp_Array1OfPnt occpoles(1, sd.control_points); int index = 1; for (auto coordinate : sd.controlx) { occpoles(index++).SetX(coordinate); } index = 1; for (auto coordinate : sd.controly) { occpoles(index++).SetY(coordinate); } index = 1; for (auto coordinate : sd.controlz) { occpoles(index++).SetZ(coordinate); } // handle knots and mults std::set unique; unique.insert(sd.knot.begin(), sd.knot.end()); int numKnots = int(unique.size()); TColStd_Array1OfInteger occmults(1, numKnots); TColStd_Array1OfReal occknots(1, numKnots); index = 1; for (auto knot : unique) { occknots(index) = knot; occmults(index) = (int)std::count(sd.knot.begin(), sd.knot.end(), knot); index++; } // handle weights TColStd_Array1OfReal occweights(1, sd.control_points); if (sd.weight.size() == std::size_t(sd.control_points)) { index = 1; for (auto weight : sd.weight) { occweights(index++) = weight; } } else { // non-rational for (int i = occweights.Lower(); i <= occweights.Upper(); i++) { occweights(i) = 1.0; } } Standard_Boolean periodic = sd.flag == 2; Handle(Geom_BSplineCurve) geom = new Geom_BSplineCurve(occpoles, occweights, occknots, occmults, sd.degree, periodic); return geom; } Handle(Geom_BSplineCurve) getInterpolationSpline(struct SplineData& sd) { std::size_t numPoints = sd.fit_points; if (sd.fitx.size() > numPoints || sd.fity.size() > numPoints || sd.fitz.size() > numPoints) { return nullptr; } // handle the poles Handle(TColgp_HArray1OfPnt) fitpoints = new TColgp_HArray1OfPnt(1, sd.fit_points); int index = 1; for (auto coordinate : sd.fitx) { fitpoints->ChangeValue(index++).SetX(coordinate); } index = 1; for (auto coordinate : sd.fity) { fitpoints->ChangeValue(index++).SetY(coordinate); } index = 1; for (auto coordinate : sd.fitz) { fitpoints->ChangeValue(index++).SetZ(coordinate); } Standard_Boolean periodic = sd.flag == 2; GeomAPI_Interpolate interp(fitpoints, periodic, Precision::Confusion()); interp.Perform(); return interp.Curve(); } void ImpExpDxfRead::OnReadSpline(struct SplineData& sd) { // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-79e1.htm // Flags: // 1: Closed, 2: Periodic, 4: Rational, 8: Planar, 16: Linear try { Handle(Geom_BSplineCurve) geom; if (sd.control_points > 0) { geom = getSplineFromPolesAndKnots(sd); } else if (sd.fit_points > 0) { geom = getInterpolationSpline(sd); } if (geom.IsNull()) { throw Standard_Failure(); } Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline"); } catch (const Standard_Failure&) { Base::Console().warning("ImpExpDxf - failed to create bspline\n"); } } // NOLINTBEGIN(bugprone-easily-swappable-parameters) void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center, double major_radius, double minor_radius, double rotation, double /*start_angle*/, double /*end_angle*/, bool dir) // NOLINTEND(bugprone-easily-swappable-parameters) { if (shouldSkipEntity()) { return; } gp_Dir up(0, 0, 1); if (!dir) { up = -up; } gp_Pnt pc = makePoint(center); gp_Elips ellipse(gp_Ax2(pc, up), major_radius, minor_radius); ellipse.Rotate(gp_Ax1(pc, up), rotation); if (ellipse.MinorRadius() > 0) { Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse"); } else { Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n"); } } void ImpExpDxfRead::OnReadText(const Base::Vector3d& point, const double height, const std::string& text, const double rotation) { if (shouldSkipEntity()) { return; } // 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 (m_importAnnotations) { auto makeText = [this, rotation, point, text, height]( 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) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) (Base::PyObjectBase*)PyObject_CallMethod(draftModule, "make_text", "sOif", text.c_str(), placement, 0, height)); Py_DECREF(placement); if (builtText != nullptr) { return dynamic_cast(builtText->getDocumentObjectPtr()); } } return nullptr; }; Collector->AddObject((FeaturePythonBuilder)makeText); } } void ImpExpDxfRead::OnReadInsert(const Base::Vector3d& point, const Base::Vector3d& scale, const std::string& name, double rotation) { if (shouldSkipEntity()) { return; } 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.contains(name)) { 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); } } } void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start, const Base::Vector3d& end, const Base::Vector3d& point, double /*rotation*/) { if (shouldSkipEntity()) { return; } if (m_importAnnotations) { auto makeDimension = [this, start, end, point](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()); } } return nullptr; }; Collector->AddObject((FeaturePythonBuilder)makeDimension); } } void ImpExpDxfRead::OnReadPolyline(std::list& vertices, int flags) { if (shouldSkipEntity()) { return; } 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"); } } ImpExpDxfRead::Layer::Layer(const std::string& name, ColorIndex_t color, std::string&& lineType, PyObject* drawingLayer) : CDxfRead::Layer(name, color, std::move(lineType)) , DraftLayerView(drawingLayer == nullptr ? Py_None : PyObject_GetAttrString(drawingLayer, "ViewObject")) , GroupContents(drawingLayer == nullptr ? nullptr : dynamic_cast( (((App::FeaturePythonPyT*)drawingLayer) ->getPropertyContainerPtr()) ->getDynamicPropertyByName("Group"))) {} ImpExpDxfRead::Layer::~Layer() { Py_XDECREF(DraftLayerView); } void ImpExpDxfRead::Layer::FinishLayer() const { if (GroupContents != 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 GroupContents->setValue(Contents); } if (DraftLayerView != Py_None && Hidden) { // 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. PyObject_CallMethod(DraftLayerView, "hide", nullptr); } } 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. Base::Color appColor = ObjectColor(color); PyObject* draftModule = nullptr; PyObject* layer = nullptr; draftModule = getDraftModule(); if (draftModule != nullptr) { // After the colours, I also want to pass the draw_style, but there is an intervening // line-width parameter. It is easier to just pass that parameter's default value than // to do the handstands to pass a named parameter. // 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"); } auto result = new Layer(name, color, std::move(lineType), layer); if (result->DraftLayerView != Py_None) { PyObject_SetAttrString(result->DraftLayerView, "OverrideLineColorChildren", Py_False); PyObject_SetAttrString(result->DraftLayerView, "OverrideShapeAppearanceChildren", 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; } return CDxfRead::MakeLayer(name, color, std::move(lineType)); } void ImpExpDxfRead::MoveToLayer(App::DocumentObject* object) const { if (m_preserveLayers) { static_cast(m_entityAttributes.m_Layer)->Contents.push_back(object); } // TODO: else Hide the object if it is in a Hidden layer? That won't work because we've cleared // out m_entityAttributes.m_Layer } std::string ImpExpDxfRead::Deformat(const char* text) { // this function removes DXF formatting from texts std::stringstream ss; bool escape = false; // turned on when finding an escape character bool longescape = false; // turned on for certain escape codes that expect additional chars for (unsigned int i = 0; i < strlen(text); i++) { char ch = text[i]; if (ch == '\\') { escape = true; } else if (escape) { if (longescape) { if (ch == ';') { escape = false; longescape = false; } } else if ((ch == 'H') || (ch == 'h') || (ch == 'Q') || (ch == 'q') || (ch == 'W') || (ch == 'w') || (ch == 'F') || (ch == 'f') || (ch == 'A') || (ch == 'a') || (ch == 'C') || (ch == 'c') || (ch == 'T') || (ch == 't')) { longescape = true; } else { if ((ch == 'P') || (ch == 'p')) { ss << "\n"; } escape = false; } } else if ((ch != '{') && (ch != '}')) { ss << ch; } } return ss.str(); } void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape, const char* nameBase) { Reader.IncrementCreatedObjectCount(); auto pcFeature = Reader.document->addObject(nameBase); pcFeature->Shape.setValue(shape); Reader.MoveToLayer(pcFeature); Reader.ApplyGuiStyles(pcFeature); } void ImpExpDxfRead::DrawingEntityCollector::AddObject(FeaturePythonBuilder shapeBuilder) { Reader.IncrementCreatedObjectCount(); App::FeaturePython* shape = shapeBuilder(Reader.OCSOrientationTransform); if (shape != nullptr) { Reader.MoveToLayer(shape); Reader.ApplyGuiStyles(shape); } } //****************************************************************************** // writing void gPntToTuple(double result[3], gp_Pnt& p) { result[0] = p.X(); result[1] = p.Y(); result[2] = p.Z(); } point3D gPntTopoint3D(gp_Pnt& p) { point3D result = {p.X(), p.Y(), p.Z()}; return result; } ImpExpDxfWrite::ImpExpDxfWrite(std::string filepath) : CDxfWrite(filepath.c_str()) { setOptionSource("User parameter:BaseApp/Preferences/Mod/Import"); setOptions(); } ImpExpDxfWrite::~ImpExpDxfWrite() = default; void ImpExpDxfWrite::setOptions() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(getOptionSource().c_str()); optionMaxLength = hGrp->GetFloat("maxsegmentlength", 5.0); optionExpPoints = hGrp->GetBool("ExportPoints", false); m_version = hGrp->GetInt("DxfVersionOut", 14); optionPolyLine = hGrp->GetBool("DiscretizeEllipses", false); m_polyOverride = hGrp->GetBool("DiscretizeEllipses", false); setDataDir(App::Application::getResourceDir() + "Mod/Import/DxfPlate/"); } void ImpExpDxfWrite::exportShape(const TopoDS_Shape input) { // export Edges TopExp_Explorer edges(input, TopAbs_EDGE); for (int i = 1; edges.More(); edges.Next(), i++) { const TopoDS_Edge& edge = TopoDS::Edge(edges.Current()); BRepAdaptor_Curve adapt(edge); if (adapt.GetType() == GeomAbs_Circle) { double f = adapt.FirstParameter(); double l = adapt.LastParameter(); gp_Pnt start = adapt.Value(f); gp_Pnt e = adapt.Value(l); if (fabs(l - f) > 1.0 && start.SquareDistance(e) < 0.001) { exportCircle(adapt); } else { exportArc(adapt); } } else if (adapt.GetType() == GeomAbs_Ellipse) { double f = adapt.FirstParameter(); double l = adapt.LastParameter(); gp_Pnt start = adapt.Value(f); gp_Pnt e = adapt.Value(l); if (fabs(l - f) > 1.0 && start.SquareDistance(e) < 0.001) { if (m_polyOverride) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else if (optionPolyLine) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else { // no overrides, do what's right! if (m_version < 14) { exportPolyline(adapt); } else { exportEllipse(adapt); } } } else { // it's an arc if (m_polyOverride) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else if (optionPolyLine) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else { // no overrides, do what's right! if (m_version < 14) { exportPolyline(adapt); } else { exportEllipseArc(adapt); } } } } else if (adapt.GetType() == GeomAbs_BSplineCurve) { if (m_polyOverride) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else if (optionPolyLine) { if (m_version >= 14) { exportLWPoly(adapt); } else { // m_version < 14 exportPolyline(adapt); } } else { // no overrides, do what's right! if (m_version < 14) { exportPolyline(adapt); } else { exportBSpline(adapt); } } } else if (adapt.GetType() == GeomAbs_BezierCurve) { exportBCurve(adapt); } else if (adapt.GetType() == GeomAbs_Line) { exportLine(adapt); } else { Base::Console().warning("ImpExpDxf - unknown curve type: %d\n", static_cast(adapt.GetType())); } } if (optionExpPoints) { TopExp_Explorer verts(input, TopAbs_VERTEX); std::vector duplicates; for (int i = 1; verts.More(); verts.Next(), i++) { const TopoDS_Vertex& v = TopoDS::Vertex(verts.Current()); gp_Pnt p = BRep_Tool::Pnt(v); duplicates.push_back(p); } std::sort(duplicates.begin(), duplicates.end(), ImpExpDxfWrite::gp_PntCompare); auto newEnd = std::unique(duplicates.begin(), duplicates.end(), ImpExpDxfWrite::gp_PntEqual); std::vector uniquePts(duplicates.begin(), newEnd); for (auto& p : uniquePts) { double point[3] = {0, 0, 0}; gPntToTuple(point, p); writePoint(point); } } } bool ImpExpDxfWrite::gp_PntEqual(gp_Pnt p1, gp_Pnt p2) { bool result = false; if (p1.IsEqual(p2, Precision::Confusion())) { result = true; } return result; } // is p1 "less than" p2? bool ImpExpDxfWrite::gp_PntCompare(gp_Pnt p1, gp_Pnt p2) { bool result = false; if (!(p1.IsEqual(p2, Precision::Confusion()))) { // ie v1 != v2 if (!(fabs(p1.X() - p2.X()) < Precision::Confusion())) { // x1 != x2 result = p1.X() < p2.X(); } else if (!(fabs(p1.Y() - p2.Y()) < Precision::Confusion())) { // y1 != y2 result = p1.Y() < p2.Y(); } else { result = p1.Z() < p2.Z(); } } return result; } void ImpExpDxfWrite::exportCircle(BRepAdaptor_Curve& c) { gp_Circ circ = c.Circle(); gp_Pnt p = circ.Location(); double center[3] = {0, 0, 0}; gPntToTuple(center, p); double radius = circ.Radius(); writeCircle(center, radius); } void ImpExpDxfWrite::exportEllipse(BRepAdaptor_Curve& c) { gp_Elips ellp = c.Ellipse(); gp_Pnt p = ellp.Location(); double center[3] = {0, 0, 0}; gPntToTuple(center, p); double major = ellp.MajorRadius(); double minor = ellp.MinorRadius(); gp_Dir xaxis = ellp.XAxis().Direction(); // direction of major axis // rotation appears to be the clockwise(?) angle between major & +Y?? double rotation = xaxis.AngleWithRef(gp_Dir(0, 1, 0), gp_Dir(0, 0, 1)); // 2*pi = 6.28319 is invalid(doesn't display in LibreCAD), but 2PI = 6.28318 is valid! // writeEllipse(center, major, minor, rotation, 0.0, 2 * std::numbers::pi, true ); writeEllipse(center, major, minor, rotation, 0.0, 6.28318, true); } void ImpExpDxfWrite::exportArc(BRepAdaptor_Curve& c) { gp_Circ circ = c.Circle(); gp_Pnt p = circ.Location(); double center[3] = {0, 0, 0}; gPntToTuple(center, p); double f = c.FirstParameter(); double l = c.LastParameter(); gp_Pnt s = c.Value(f); double start[3]; gPntToTuple(start, s); gp_Pnt m = c.Value((l + f) / 2.0); gp_Pnt e = c.Value(l); double end[3] = {0, 0, 0}; gPntToTuple(end, e); gp_Vec v1(m, s); gp_Vec v2(m, e); gp_Vec v3(0, 0, 1); double a = v3.DotCross(v1, v2); bool dir = (a < 0) ? true : false; writeArc(start, end, center, dir); } void ImpExpDxfWrite::exportEllipseArc(BRepAdaptor_Curve& c) { gp_Elips ellp = c.Ellipse(); gp_Pnt p = ellp.Location(); double center[3] = {0, 0, 0}; gPntToTuple(center, p); double major = ellp.MajorRadius(); double minor = ellp.MinorRadius(); gp_Dir xaxis = ellp.XAxis().Direction(); // direction of major axis // rotation appears to be the clockwise angle between major & +Y?? double rotation = xaxis.AngleWithRef(gp_Dir(0, 1, 0), gp_Dir(0, 0, 1)); double f = c.FirstParameter(); double l = c.LastParameter(); gp_Pnt s = c.Value(f); gp_Pnt m = c.Value((l + f) / 2.0); gp_Pnt e = c.Value(l); gp_Vec v1(m, s); gp_Vec v2(m, e); gp_Vec v3(0, 0, 1); double a = v3.DotCross(v1, v2); // a = v3 dot (v1 cross v2) // relates to "handedness" of 3 vectors // a > 0 ==> v2 is CCW from v1 (righthanded)? // a < 0 ==> v2 is CW from v1 (lefthanded)? double startAngle = fmod(f, 2.0 * std::numbers::pi); // revolutions double endAngle = fmod(l, 2.0 * std::numbers::pi); bool endIsCW = (a < 0) ? true : false; // if !endIsCW swap(start,end) // not sure if this is a hack or not. seems to make valid arcs. if (!endIsCW) { startAngle = -startAngle; endAngle = -endAngle; } writeEllipse(center, major, minor, rotation, startAngle, endAngle, endIsCW); } void ImpExpDxfWrite::exportBSpline(BRepAdaptor_Curve& c) { SplineDataOut sd; Handle(Geom_BSplineCurve) spline; double f, l; gp_Pnt s, ePt; Standard_Real tol3D = 0.001; Standard_Integer maxDegree = 3, maxSegment = 200; Handle(BRepAdaptor_HCurve) hCurve = new BRepAdaptor_HCurve(c); Approx_Curve3d approx(hCurve, tol3D, GeomAbs_C0, maxSegment, maxDegree); if (approx.IsDone() && approx.HasResult()) { spline = approx.Curve(); } else { if (approx.HasResult()) { // result, but not within tolerance spline = approx.Curve(); Base::Console().message("DxfWrite::exportBSpline - result not within tolerance\n"); } else { f = c.FirstParameter(); l = c.LastParameter(); s = c.Value(f); ePt = c.Value(l); Base::Console().message( "DxfWrite::exportBSpline - no result- from:(%.3f,%.3f) to:(%.3f,%.3f)\n", s.X(), s.Y(), ePt.X(), ePt.Y()); TColgp_Array1OfPnt controlPoints(0, 1); controlPoints.SetValue(0, s); controlPoints.SetValue(1, ePt); spline = GeomAPI_PointsToBSpline(controlPoints, 1).Curve(); } } // WF? norm of surface containing curve?? sd.norm.x = 0.0; sd.norm.y = 0.0; sd.norm.z = 1.0; sd.flag = spline->IsClosed(); sd.flag += spline->IsPeriodic() * 2; sd.flag += spline->IsRational() * 4; sd.flag += 8; // planar spline sd.degree = spline->Degree(); sd.control_points = spline->NbPoles(); sd.knots = spline->NbKnots(); gp_Pnt p; spline->D0(spline->FirstParameter(), p); sd.starttan = gPntTopoint3D(p); spline->D0(spline->LastParameter(), p); sd.endtan = gPntTopoint3D(p); // next bit is from DrawingExport.cpp (Dan Falk?). Standard_Integer m = 0; if (spline->IsPeriodic()) { m = spline->NbPoles() + 2 * spline->Degree() - spline->Multiplicity(1) + 2; } else { for (int i = 1; i <= spline->NbKnots(); i++) { m += spline->Multiplicity(i); } } TColStd_Array1OfReal knotsequence(1, m); spline->KnotSequence(knotsequence); for (int i = knotsequence.Lower(); i <= knotsequence.Upper(); i++) { sd.knot.push_back(knotsequence(i)); } sd.knots = knotsequence.Length(); TColgp_Array1OfPnt poles(1, spline->NbPoles()); spline->Poles(poles); for (int i = poles.Lower(); i <= poles.Upper(); i++) { sd.control.push_back(gPntTopoint3D(poles(i))); } // OCC doesn't have separate lists for control points and fit points. writeSpline(sd); } void ImpExpDxfWrite::exportBCurve(BRepAdaptor_Curve& c) { (void)c; Base::Console().message("BCurve dxf export not yet supported\n"); } void ImpExpDxfWrite::exportLine(BRepAdaptor_Curve& c) { double f = c.FirstParameter(); double l = c.LastParameter(); gp_Pnt s = c.Value(f); double start[3] = {0, 0, 0}; gPntToTuple(start, s); gp_Pnt e = c.Value(l); double end[3] = {0, 0, 0}; gPntToTuple(end, e); writeLine(start, end); } void ImpExpDxfWrite::exportLWPoly(BRepAdaptor_Curve& c) { LWPolyDataOut pd; pd.Flag = c.IsClosed(); pd.Elev = 0.0; pd.Thick = 0.0; pd.Extr.x = 0.0; pd.Extr.y = 0.0; pd.Extr.z = 1.0; pd.nVert = 0; GCPnts_UniformAbscissa discretizer; discretizer.Initialize(c, optionMaxLength); std::vector points; if (discretizer.IsDone() && discretizer.NbPoints() > 0) { int nbPoints = discretizer.NbPoints(); for (int i = 1; i <= nbPoints; i++) { gp_Pnt p = c.Value(discretizer.Parameter(i)); pd.Verts.push_back(gPntTopoint3D(p)); } pd.nVert = discretizer.NbPoints(); writeLWPolyLine(pd); } } void ImpExpDxfWrite::exportPolyline(BRepAdaptor_Curve& c) { LWPolyDataOut pd; pd.Flag = c.IsClosed(); pd.Elev = 0.0; pd.Thick = 0.0; pd.Extr.x = 0.0; pd.Extr.y = 0.0; pd.Extr.z = 1.0; pd.nVert = 0; GCPnts_UniformAbscissa discretizer; discretizer.Initialize(c, optionMaxLength); std::vector points; if (discretizer.IsDone() && discretizer.NbPoints() > 0) { int nbPoints = discretizer.NbPoints(); for (int i = 1; i <= nbPoints; i++) { gp_Pnt p = c.Value(discretizer.Parameter(i)); pd.Verts.push_back(gPntTopoint3D(p)); } pd.nVert = discretizer.NbPoints(); writePolyline(pd); } } void ImpExpDxfWrite::exportText(const char* text, Base::Vector3d position1, Base::Vector3d position2, double size, int just) { double location1[3] = {0, 0, 0}; location1[0] = position1.x; location1[1] = position1.y; location1[2] = position1.z; double location2[3] = {0, 0, 0}; location2[0] = position2.x; location2[1] = position2.y; location2[2] = position2.z; writeText(text, location1, location2, size, just); } void ImpExpDxfWrite::exportLinearDim(Base::Vector3d textLocn, Base::Vector3d lineLocn, Base::Vector3d extLine1Start, Base::Vector3d extLine2Start, char* dimText, int type) { double text[3] = {0, 0, 0}; text[0] = textLocn.x; text[1] = textLocn.y; text[2] = textLocn.z; double line[3] = {0, 0, 0}; line[0] = lineLocn.x; line[1] = lineLocn.y; line[2] = lineLocn.z; double ext1[3] = {0, 0, 0}; ext1[0] = extLine1Start.x; ext1[1] = extLine1Start.y; ext1[2] = extLine1Start.z; double ext2[3] = {0, 0, 0}; ext2[0] = extLine2Start.x; ext2[1] = extLine2Start.y; ext2[2] = extLine2Start.z; writeLinearDim(text, line, ext1, ext2, dimText, type); } void ImpExpDxfWrite::exportAngularDim(Base::Vector3d textLocn, Base::Vector3d lineLocn, Base::Vector3d extLine1End, Base::Vector3d extLine2End, Base::Vector3d apexPoint, char* dimText) { double text[3] = {0, 0, 0}; text[0] = textLocn.x; text[1] = textLocn.y; text[2] = textLocn.z; double line[3] = {0, 0, 0}; line[0] = lineLocn.x; line[1] = lineLocn.y; line[2] = lineLocn.z; double ext1[3] = {0, 0, 0}; ext1[0] = extLine1End.x; ext1[1] = extLine1End.y; ext1[2] = extLine1End.z; double ext2[3] = {0, 0, 0}; ext2[0] = extLine2End.x; ext2[1] = extLine2End.y; ext2[2] = extLine2End.z; double apex[3] = {0, 0, 0}; apex[0] = apexPoint.x; apex[1] = apexPoint.y; apex[2] = apexPoint.z; writeAngularDim(text, line, apex, ext1, apex, ext2, dimText); } void ImpExpDxfWrite::exportRadialDim(Base::Vector3d centerPoint, Base::Vector3d textLocn, Base::Vector3d arcPoint, char* dimText) { double center[3] = {0, 0, 0}; center[0] = centerPoint.x; center[1] = centerPoint.y; center[2] = centerPoint.z; double text[3] = {0, 0, 0}; text[0] = textLocn.x; text[1] = textLocn.y; text[2] = textLocn.z; double arc[3] = {0, 0, 0}; arc[0] = arcPoint.x; arc[1] = arcPoint.y; arc[2] = arcPoint.z; writeRadialDim(center, text, arc, dimText); } void ImpExpDxfWrite::exportDiametricDim(Base::Vector3d textLocn, Base::Vector3d arcPoint1, Base::Vector3d arcPoint2, char* dimText) { double text[3] = {0, 0, 0}; text[0] = textLocn.x; text[1] = textLocn.y; text[2] = textLocn.z; double arc1[3] = {0, 0, 0}; arc1[0] = arcPoint1.x; arc1[1] = arcPoint1.y; arc1[2] = arcPoint1.z; double arc2[3] = {0, 0, 0}; arc2[0] = arcPoint2.x; arc2[1] = arcPoint2.y; arc2[2] = arcPoint2.z; writeDiametricDim(text, arc1, arc2, dimText); }