Download src/Mod/Import/App/dxf/ImpExpDxf.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 80.8 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Import/App/dxf/ImpExpDxf.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Import/App/dxf/ImpExpDxf.cpp
-
curl -L -o ImpExpDxf.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Import/App/dxf/ImpExpDxf.cpp
80.8 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * 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 * | |
| * * | |
| ***************************************************************************/ | |
| using namespace Import; | |
| using BRepAdaptor_HCurve = BRepAdaptor_Curve; | |
| namespace | |
| { | |
| Part::Circle* createCirclePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name); | |
| Part::Line* createLinePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name); | |
| Part::Ellipse* createEllipsePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name); | |
| Part::Vertex* createVertexPrimitive(const TopoDS_Vertex& vertex, App::Document* doc, const char* name); | |
| Part::Feature* createGenericShapeFeature(const TopoDS_Shape& shape, App::Document* doc, const char* name); | |
| } // namespace | |
| namespace | |
| { | |
| // Helper function to create and configure a Part::Ellipse primitive from a TopoDS_Edge | |
| Part::Ellipse* createEllipsePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name) | |
| { | |
| auto* p = doc->addObject<Part::Ellipse>(name); | |
| if (!p) { | |
| return nullptr; | |
| } | |
| TopLoc_Location loc; | |
| Standard_Real first, last; | |
| Handle(Geom_Curve) aCurve = BRep_Tool::Curve(edge, loc, first, last); | |
| if (aCurve->IsInstance(Geom_Ellipse::get_type_descriptor())) { | |
| Handle(Geom_Ellipse) ellipse = Handle(Geom_Ellipse)::DownCast(aCurve); | |
| // Set parametric properties | |
| p->MajorRadius.setValue(ellipse->MajorRadius()); | |
| p->MinorRadius.setValue(ellipse->MinorRadius()); | |
| // The axis contains the full transformation (location and orientation). | |
| // It's crucial to apply the TopLoc_Location transformation from the edge. | |
| gp_Ax2 axis = ellipse->Position().Transformed(loc.Transformation()); | |
| gp_Pnt center = axis.Location(); | |
| gp_Dir xDir = axis.XDirection(); // Major Axis Direction | |
| gp_Dir yDir = axis.YDirection(); // Minor Axis Direction | |
| gp_Dir zDir = axis.Direction(); // Normal | |
| Base::Placement plc; | |
| plc.setPosition(Base::Vector3d(center.X(), center.Y(), center.Z())); | |
| plc.setRotation( | |
| Base::Rotation::makeRotationByAxes( | |
| Base::Vector3d(xDir.X(), xDir.Y(), xDir.Z()), | |
| Base::Vector3d(yDir.X(), yDir.Y(), yDir.Z()), | |
| Base::Vector3d(zDir.X(), zDir.Y(), zDir.Z()) | |
| ) | |
| ); | |
| p->Placement.setValue(plc); | |
| // Set angles for arcs, converting from radians (OCC) to degrees (PropertyAngle) | |
| BRep_Tool::Range(edge, first, last); | |
| p->Angle1.setValue(Base::toDegrees(first)); | |
| p->Angle2.setValue(Base::toDegrees(last)); | |
| } | |
| return p; | |
| } | |
| // Helper function to create and configure a Part::Circle primitive from a TopoDS_Edge | |
| Part::Circle* createCirclePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name) | |
| { | |
| auto* p = doc->addObject<Part::Circle>(name); | |
| if (!p) { | |
| return nullptr; | |
| } | |
| TopLoc_Location loc; | |
| Standard_Real first, last; | |
| Handle(Geom_Curve) aCurve = BRep_Tool::Curve(edge, loc, first, last); | |
| if (aCurve->IsInstance(Geom_Circle::get_type_descriptor())) { | |
| Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(aCurve); | |
| p->Radius.setValue(circle->Radius()); | |
| // The axis contains the full transformation (location and orientation). | |
| gp_Ax2 axis = circle->Position().Transformed(loc.Transformation()); | |
| gp_Pnt center = axis.Location(); | |
| gp_Dir xDir = axis.XDirection(); | |
| gp_Dir yDir = axis.YDirection(); | |
| gp_Dir zDir = axis.Direction(); | |
| Base::Placement plc; | |
| plc.setPosition(Base::Vector3d(center.X(), center.Y(), center.Z())); | |
| plc.setRotation( | |
| Base::Rotation::makeRotationByAxes( | |
| Base::Vector3d(xDir.X(), xDir.Y(), xDir.Z()), | |
| Base::Vector3d(yDir.X(), yDir.Y(), yDir.Z()), | |
| Base::Vector3d(zDir.X(), zDir.Y(), zDir.Z()) | |
| ) | |
| ); | |
| p->Placement.setValue(plc); | |
| // Set angles for arcs | |
| BRep_Tool::Range(edge, first, last); | |
| p->Angle1.setValue(Base::toDegrees(first)); | |
| p->Angle2.setValue(Base::toDegrees(last)); | |
| } | |
| return p; | |
| } | |
| // Helper function to create and configure a Part::Line primitive from a TopoDS_Edge | |
| Part::Line* createLinePrimitive(const TopoDS_Edge& edge, App::Document* doc, const char* name) | |
| { | |
| auto* p = doc->addObject<Part::Line>(name); | |
| if (!p) { | |
| return nullptr; | |
| } | |
| TopoDS_Vertex v1, v2; | |
| TopExp::Vertices(edge, v1, v2); | |
| gp_Pnt p1 = BRep_Tool::Pnt(v1); | |
| gp_Pnt p2 = BRep_Tool::Pnt(v2); | |
| p->X1.setValue(p1.X()); | |
| p->Y1.setValue(p1.Y()); | |
| p->Z1.setValue(p1.Z()); | |
| p->X2.setValue(p2.X()); | |
| p->Y2.setValue(p2.Y()); | |
| p->Z2.setValue(p2.Z()); | |
| return p; | |
| } | |
| // Helper function to create and configure a Part::Vertex primitive from a TopoDS_Vertex | |
| Part::Vertex* createVertexPrimitive(const TopoDS_Vertex& vertex, App::Document* doc, const char* name) | |
| { | |
| auto* p = doc->addObject<Part::Vertex>(name); | |
| if (p) { | |
| gp_Pnt pnt = BRep_Tool::Pnt(vertex); | |
| p->X.setValue(pnt.X()); | |
| p->Y.setValue(pnt.Y()); | |
| p->Z.setValue(pnt.Z()); | |
| } | |
| return p; | |
| } | |
| // Helper function to create a generic Part::Feature for any non-parametric shape | |
| Part::Feature* createGenericShapeFeature(const TopoDS_Shape& shape, App::Document* doc, const char* name) | |
| { | |
| auto* p = doc->addObject<Part::Feature>(name); | |
| if (p) { | |
| p->Shape.setValue(shape); | |
| } | |
| return p; | |
| } | |
| } // namespace | |
| TopoDS_Wire ImpExpDxfRead::BuildWireFromPolyline(std::list<VertexInfo>& vertices, int flags) | |
| { | |
| BRepBuilderAPI_MakeWire wireBuilder; | |
| bool is_closed = ((flags & 1) != 0); | |
| if (vertices.empty()) { | |
| return wireBuilder.Wire(); | |
| } | |
| auto it = vertices.begin(); | |
| auto prev_it = it++; | |
| while (it != vertices.end()) { | |
| const VertexInfo& start_vertex = *prev_it; | |
| const VertexInfo& end_vertex = *it; | |
| TopoDS_Edge edge; | |
| if (start_vertex.bulge == 0.0) { | |
| edge = BRepBuilderAPI_MakeEdge( | |
| makePoint(start_vertex.location), | |
| makePoint(end_vertex.location) | |
| ) | |
| .Edge(); | |
| } | |
| else { | |
| double cot = ((1.0 / start_vertex.bulge) - start_vertex.bulge) / 2.0; | |
| double center_x = ((start_vertex.location.x + end_vertex.location.x) | |
| - (end_vertex.location.y - start_vertex.location.y) * cot) | |
| / 2.0; | |
| double center_y = ((start_vertex.location.y + end_vertex.location.y) | |
| + (end_vertex.location.x - start_vertex.location.x) * cot) | |
| / 2.0; | |
| double center_z = (start_vertex.location.z + end_vertex.location.z) / 2.0; | |
| Base::Vector3d center(center_x, center_y, center_z); | |
| gp_Pnt p0 = makePoint(start_vertex.location); | |
| gp_Pnt p1 = makePoint(end_vertex.location); | |
| gp_Dir up(0, 0, 1); | |
| if (start_vertex.bulge < 0) { | |
| up.Reverse(); | |
| } | |
| gp_Pnt pc = makePoint(center); | |
| gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); | |
| if (circle.Radius() > 1e-9) { | |
| edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); | |
| } | |
| } | |
| if (!edge.IsNull()) { | |
| wireBuilder.Add(edge); | |
| } | |
| prev_it = it++; | |
| } | |
| if (is_closed && vertices.size() > 1) { | |
| const VertexInfo& start_vertex = vertices.back(); | |
| const VertexInfo& end_vertex = vertices.front(); | |
| // check if the vertices are coincident (distance < tolerance) | |
| // if they are, the polyline is already closed and we don't need a closing edge | |
| gp_Pnt p0 = makePoint(start_vertex.location); | |
| gp_Pnt p1 = makePoint(end_vertex.location); | |
| double distance = p0.Distance(p1); | |
| if (distance > Precision::Confusion()) { | |
| TopoDS_Edge edge; | |
| if (start_vertex.bulge == 0.0) { | |
| edge = BRepBuilderAPI_MakeEdge(p0, p1).Edge(); | |
| } | |
| else { | |
| double cot = ((1.0 / start_vertex.bulge) - start_vertex.bulge) / 2.0; | |
| double center_x = ((start_vertex.location.x + end_vertex.location.x) | |
| - (end_vertex.location.y - start_vertex.location.y) * cot) | |
| / 2.0; | |
| double center_y = ((start_vertex.location.y + end_vertex.location.y) | |
| + (end_vertex.location.x - start_vertex.location.x) * cot) | |
| / 2.0; | |
| double center_z = (start_vertex.location.z + end_vertex.location.z) / 2.0; | |
| Base::Vector3d center(center_x, center_y, center_z); | |
| gp_Dir up(0, 0, 1); | |
| if (start_vertex.bulge < 0) { | |
| up.Reverse(); | |
| } | |
| gp_Pnt pc = makePoint(center); | |
| gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); | |
| if (circle.Radius() > 1e-9) { | |
| edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); | |
| } | |
| } | |
| if (!edge.IsNull()) { | |
| wireBuilder.Add(edge); | |
| } | |
| } | |
| } | |
| return wireBuilder.Wire(); | |
| } | |
| Part::Feature* ImpExpDxfRead::createFlattenedPolylineFeature(const TopoDS_Wire& wire, const char* name) | |
| { | |
| auto* p = document->addObject<Part::Feature>(document->getUniqueObjectName(name).c_str()); | |
| if (p) { | |
| p->Shape.setValue(wire); | |
| IncrementCreatedObjectCount(); | |
| } | |
| return p; | |
| } | |
| Part::Compound* ImpExpDxfRead::createParametricPolylineCompound(const TopoDS_Wire& wire, const char* name) | |
| { | |
| auto* p = document->addObject<Part::Compound>(document->getUniqueObjectName(name).c_str()); | |
| IncrementCreatedObjectCount(); | |
| std::vector<App::DocumentObject*> segments; | |
| TopExp_Explorer explorer(wire, TopAbs_EDGE); | |
| for (; explorer.More(); explorer.Next()) { | |
| TopoDS_Edge edge = TopoDS::Edge(explorer.Current()); | |
| App::DocumentObject* segment = nullptr; | |
| BRepAdaptor_Curve adaptor(edge); | |
| if (adaptor.GetType() == GeomAbs_Line) { | |
| segment = createLinePrimitive(edge, document, "Segment"); | |
| } | |
| else if (adaptor.GetType() == GeomAbs_Circle) { | |
| segment = createCirclePrimitive(edge, document, "Arc"); | |
| } | |
| if (segment) { | |
| IncrementCreatedObjectCount(); | |
| segment->Visibility.setValue(false); | |
| // We apply styles later, depending on the context | |
| segments.push_back(segment); | |
| } | |
| } | |
| p->Links.setValues(segments); | |
| return p; | |
| } | |
| void ImpExpDxfRead::CreateFlattenedPolyline(const TopoDS_Wire& wire, const char* name) | |
| { | |
| Part::Feature* p = createFlattenedPolylineFeature(wire, name); | |
| // Perform the context-specific action of adding it to the collector | |
| if (p) { | |
| Collector->AddObject(p, name); | |
| } | |
| } | |
| void ImpExpDxfRead::CreateParametricPolyline(const TopoDS_Wire& wire, const char* name) | |
| { | |
| Part::Compound* p = createParametricPolylineCompound(wire, name); | |
| // Perform the context-specific actions (applying styles and adding to the document) | |
| if (p) { | |
| // Style the child segments | |
| for (App::DocumentObject* segment : p->Links.getValues()) { | |
| ApplyGuiStyles(static_cast<Part::Feature*>(segment)); | |
| } | |
| // Add the final compound object to the document | |
| Collector->AddObject(p, name); | |
| } | |
| } | |
| std::map<std::string, int> ImpExpDxfRead::PreScan(const std::string& filepath) | |
| { | |
| std::map<std::string, int> counts; | |
| std::ifstream ifs(filepath); | |
| if (!ifs) { | |
| // Could throw an exception or log an error | |
| return counts; | |
| } | |
| std::string line; | |
| bool next_is_entity_name = false; | |
| while (std::getline(ifs, line)) { | |
| // Simple trim for Windows-style carriage returns | |
| if (!line.empty() && line.back() == '\r') { | |
| line.pop_back(); | |
| } | |
| if (next_is_entity_name) { | |
| // The line after a " 0" group code is the entity type | |
| counts[line]++; | |
| next_is_entity_name = false; | |
| } | |
| else if (line == " 0") { | |
| next_is_entity_name = true; | |
| } | |
| } | |
| return counts; | |
| } | |
| //****************************************************************************** | |
| // reading | |
| ImpExpDxfRead::ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc) | |
| : CDxfRead(filepath) | |
| , document(pcDoc) | |
| { | |
| setOptionSource("User parameter:BaseApp/Preferences/Mod/Draft"); | |
| setOptions(); | |
| } | |
| void ImpExpDxfRead::StartImport() | |
| { | |
| CDxfRead::StartImport(); | |
| // Create a hidden group to store the base objects for block definitions | |
| m_blockDefinitionGroup = static_cast<App::DocumentObjectGroup*>( | |
| document->addObject("App::DocumentObjectGroup", "_BlockDefinitions") | |
| ); | |
| m_blockDefinitionGroup->Visibility.setValue(false); | |
| // Create a hidden group to store unreferenced blocks | |
| m_unreferencedBlocksGroup = static_cast<App::DocumentObjectGroup*>( | |
| document->addObject("App::DocumentObjectGroup", "_UnreferencedBlocks") | |
| ); | |
| m_unreferencedBlocksGroup->Visibility.setValue(false); | |
| } | |
| bool ImpExpDxfRead::ReadEntitiesSection() | |
| { | |
| // After parsing the BLOCKS section, compose all block definitions | |
| // into FreeCAD objects before processing the ENTITIES section. | |
| ComposeBlocks(); | |
| DrawingEntityCollector collector(*this); | |
| if (m_importMode == ImportMode::FusedShapes) { | |
| std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>> 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<TopoDS_Shape>& 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); | |
| } | |
| } | |
| TopoDS_Shape ImpExpDxfRead::CombineShapesToCompound(const std::list<TopoDS_Shape>& shapes) const | |
| { | |
| if (shapes.empty()) { | |
| return TopoDS_Shape(); | |
| } | |
| BRep_Builder builder; | |
| TopoDS_Compound comp; | |
| builder.MakeCompound(comp); | |
| for (const auto& sh : shapes) { | |
| if (!sh.IsNull()) { | |
| builder.Add(comp, sh); | |
| } | |
| } | |
| return comp; | |
| } | |
| 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"; | |
| // Read the new master import mode parameter, set the default. | |
| int mode = hGrp->GetInt("DxfImportMode", static_cast<int>(ImportMode::IndividualShapes)); | |
| m_importMode = static_cast<ImportMode>(mode); | |
| // 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. | |
| // Update: The "Join geometry" option is now a checkbox that is only enabled for the legacy | |
| // importer. Whether the modern importer should support this is still up for debate. | |
| 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); | |
| } | |
| void ImpExpDxfRead::ComposeFlattenedBlock(const std::string& blockName, std::set<std::string>& composed) | |
| { | |
| // 1. Base Case: If already composed, do nothing. | |
| if (composed.count(blockName)) { | |
| return; | |
| } | |
| // 2. Find the raw block data. | |
| auto it = this->Blocks.find(blockName); | |
| if (it == this->Blocks.end()) { | |
| ImportError("Block '%s' is referenced but not defined. Skipping.", blockName.c_str()); | |
| return; | |
| } | |
| const Block& blockData = it->second; | |
| // 3. Collect all geometry shapes for this block. | |
| std::list<TopoDS_Shape> shapeCollection; | |
| // 4. Process primitive geometry. | |
| for (const auto& [attributes, builderList] : blockData.GeometryBuilders) { | |
| for (const auto& builder : builderList) { | |
| shapeCollection.push_back(builder.shape); | |
| } | |
| } | |
| // 5. Process nested inserts recursively. | |
| for (const auto& insertAttrPair : blockData.Inserts) { | |
| for (const auto& nestedInsert : insertAttrPair.second) { | |
| // Ensure the nested block is composed first. | |
| ComposeFlattenedBlock(nestedInsert.Name, composed); | |
| // Mark the nested block as referenced so it's not moved to the "Unreferenced" group. | |
| m_referencedBlocks.insert(nestedInsert.Name); | |
| // Retrieve the final, flattened shape of the nested block. | |
| auto shape_it = m_flattenedBlockShapes.find(nestedInsert.Name); | |
| if (shape_it != m_flattenedBlockShapes.end()) { | |
| if (!shape_it->second.IsNull()) { | |
| // Use the Part::TopoShape wrapper to access the transformShape method. | |
| Part::TopoShape nestedShape(shape_it->second); | |
| // Apply the insert's transformation. | |
| Base::Placement pl( | |
| nestedInsert.Point, | |
| Base::Rotation(Base::Vector3d(0, 0, 1), nestedInsert.Rotation) | |
| ); | |
| Base::Matrix4D transform = pl.toMatrix(); | |
| transform.scale(nestedInsert.Scale); | |
| nestedShape.transformShape(transform, true, true); // Use copy=true | |
| shapeCollection.push_back(nestedShape.getShape()); | |
| } | |
| } | |
| } | |
| } | |
| // 6. Build the final merged shape. | |
| TopoDS_Shape finalShape = CombineShapesToCompound(shapeCollection); | |
| m_flattenedBlockShapes[blockName] = finalShape; // Cache the result. | |
| // 7. Create the final Part::Feature object. | |
| if (!finalShape.IsNull()) { | |
| std::string featureName = "BLOCK_" + blockName; | |
| auto blockFeature = document->addObject<Part::Feature>( | |
| document->getUniqueObjectName(featureName.c_str()).c_str() | |
| ); | |
| blockFeature->Shape.setValue(finalShape); | |
| blockFeature->Visibility.setValue(false); | |
| m_blockDefinitionGroup->addObject(blockFeature); | |
| this->m_blockDefinitions[blockName] = blockFeature; | |
| } | |
| // 8. Mark this block as composed. | |
| composed.insert(blockName); | |
| } | |
| void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName, std::set<std::string>& composed) | |
| { | |
| // 1. Base Case: If this block has already been composed, we're done. | |
| if (composed.count(blockName)) { | |
| return; | |
| } | |
| // 2. Find the raw block data from the parsing phase. | |
| auto it = this->Blocks.find(blockName); | |
| if (it == this->Blocks.end()) { | |
| ImportError("Block '%s' is referenced but not defined. Skipping.", blockName.c_str()); | |
| return; | |
| } | |
| const Block& blockData = it->second; | |
| // 3. Create the master Part::Compound for this block definition. | |
| std::string compName = "BLOCK_" + blockName; | |
| auto blockCompound = document->addObject<Part::Compound>( | |
| document->getUniqueObjectName(compName.c_str()).c_str() | |
| ); | |
| m_blockDefinitionGroup->addObject(blockCompound); | |
| IncrementCreatedObjectCount(); | |
| blockCompound->Visibility.setValue(false); | |
| this->m_blockDefinitions[blockName] = blockCompound; | |
| std::vector<App::DocumentObject*> childObjects; | |
| // 4. Recursively Compose and Link Nested Inserts. | |
| for (const auto& insertAttrPair : blockData.Inserts) { | |
| for (const auto& nestedInsert : insertAttrPair.second) { | |
| // Ensure the dependency is composed before we try to link to it. | |
| ComposeParametricBlock(nestedInsert.Name, composed); | |
| // Mark the nested block as referenced so it's not moved to the "Unreferenced" group. | |
| m_referencedBlocks.insert(nestedInsert.Name); | |
| // Create the App::Link for this nested insert. | |
| auto baseObjIt = m_blockDefinitions.find(nestedInsert.Name); | |
| if (baseObjIt != m_blockDefinitions.end()) { | |
| // The link's name should be based on the block it is inserting, not the parent. | |
| std::string linkName = "Link_" + nestedInsert.Name; | |
| auto link = document->addObject<App::Link>( | |
| document->getUniqueObjectName(linkName.c_str()).c_str() | |
| ); | |
| link->setLink(-1, baseObjIt->second); | |
| link->LinkTransform.setValue(false); | |
| // Apply placement and scale to the link itself. | |
| Base::Placement pl( | |
| nestedInsert.Point, | |
| Base::Rotation(Base::Vector3d(0, 0, 1), nestedInsert.Rotation) | |
| ); | |
| link->Placement.setValue(pl); | |
| link->ScaleVector.setValue(nestedInsert.Scale); | |
| link->Visibility.setValue(false); | |
| IncrementCreatedObjectCount(); | |
| childObjects.push_back(link); | |
| } | |
| } | |
| } | |
| // 5. Create and Link Primitive Geometry from the collected builders. | |
| for (const auto& [attributes, builderList] : blockData.GeometryBuilders) { | |
| this->m_entityAttributes = attributes; // Set attributes for layer/color handling | |
| for (const auto& builder : builderList) { | |
| App::DocumentObject* newObject = nullptr; | |
| switch (builder.type) { | |
| // Existing cases for other primitives | |
| case GeometryBuilder::PrimitiveType::Line: { | |
| newObject = createLinePrimitive(TopoDS::Edge(builder.shape), document, "Line"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Point: { | |
| newObject = createVertexPrimitive(TopoDS::Vertex(builder.shape), document, "Point"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Circle: | |
| case GeometryBuilder::PrimitiveType::Arc: { | |
| const char* name = (builder.type == GeometryBuilder::PrimitiveType::Circle) | |
| ? "Circle" | |
| : "Arc"; | |
| auto* p = createCirclePrimitive(TopoDS::Edge(builder.shape), document, name); | |
| if (!p) { | |
| break; | |
| } | |
| if (builder.type == GeometryBuilder::PrimitiveType::Circle) { | |
| p->Angle1.setValue(0.0); | |
| p->Angle2.setValue(360.0); | |
| } | |
| newObject = p; | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Ellipse: { | |
| newObject | |
| = createEllipsePrimitive(TopoDS::Edge(builder.shape), document, "Ellipse"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Spline: { | |
| // Splines are generic Part::Feature as no Part primitive exists | |
| auto* p = document->addObject<Part::Feature>("Spline"); | |
| p->Shape.setValue(builder.shape); | |
| newObject = p; | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::PolylineFlattened: { | |
| // This creates a simple Part::Feature wrapping the wire, which is standard for | |
| // block children. | |
| newObject = createFlattenedPolylineFeature(TopoDS::Wire(builder.shape), "Polyline"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::PolylineParametric: { | |
| // This creates a Part::Compound containing line/arc segments. | |
| newObject | |
| = createParametricPolylineCompound(TopoDS::Wire(builder.shape), "Polyline"); | |
| // No styling needed here, as the block's instance will control appearance. | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::None: // Default/fallback if not handled | |
| default: { | |
| // Generic shape, e.g., 3DFACE | |
| newObject = createGenericShapeFeature(builder.shape, document, "Shape"); | |
| break; | |
| } | |
| } | |
| if (newObject) { | |
| IncrementCreatedObjectCount(); | |
| newObject->Visibility.setValue(false); // Children of blocks are hidden by default | |
| // Layer and color are applied by the block itself (Part::Compound) or its children | |
| // if overridden. | |
| ApplyGuiStyles(static_cast<Part::Feature*>(newObject)); // Apply style to the child | |
| // object | |
| childObjects.push_back(newObject); // Add to the block's main children list | |
| } | |
| } | |
| } | |
| // 6. Finalize the Part::Compound. | |
| if (!childObjects.empty()) { | |
| blockCompound->Links.setValues(childObjects); | |
| } | |
| // 7. Mark this block as composed. | |
| composed.insert(blockName); | |
| } | |
| void ImpExpDxfRead::ComposeBlocks() | |
| { | |
| std::set<std::string> composedBlocks; | |
| if (m_importMode == ImportMode::FusedShapes) { | |
| // User wants flattened geometry for performance. | |
| for (const auto& pair : this->Blocks) { | |
| if (composedBlocks.find(pair.first) == composedBlocks.end()) { | |
| ComposeFlattenedBlock(pair.first, composedBlocks); | |
| } | |
| } | |
| } | |
| else { | |
| // User wants a parametric, editable structure. | |
| for (const auto& pair : this->Blocks) { | |
| if (composedBlocks.find(pair.first) == composedBlocks.end()) { | |
| ComposeParametricBlock(pair.first, composedBlocks); | |
| } | |
| } | |
| } | |
| } | |
| void ImpExpDxfRead::FinishImport() | |
| { | |
| // This function runs after all blocks have been parsed and composed. | |
| // It sorts all created block definitions into two groups: those that are | |
| // actively referenced in the drawing, and those that are not. | |
| std::vector<App::DocumentObject*> referenced; | |
| std::vector<App::DocumentObject*> unreferenced; | |
| for (const auto& pair : m_blockDefinitions) { | |
| const std::string& blockName = pair.first; | |
| App::DocumentObject* blockObj = pair.second; | |
| bool is_referenced = (m_referencedBlocks.find(blockName) != m_referencedBlocks.end()); | |
| // A block is considered "referenced" if it was explicitly inserted | |
| // or if it is an anonymous system block (e.g., for dimensions). | |
| // All other named blocks are considered unreferenced if not found in the set. | |
| if (is_referenced || (blockName.rfind('*', 0) == 0)) { | |
| referenced.push_back(blockObj); | |
| } | |
| else { | |
| unreferenced.push_back(blockObj); | |
| } | |
| } | |
| // Re-assign the group contents by setting the PropertyLinkList for each group. | |
| // This correctly re-parents the objects in the document's dependency graph. | |
| m_blockDefinitionGroup->Group.setValues(referenced); | |
| m_unreferencedBlocksGroup->Group.setValues(unreferenced); | |
| // Final cleanup: If the unreferenced group is empty, remove it to avoid | |
| // unnecessary clutter in the document tree. Otherwise, ensure it's hidden. | |
| if (unreferenced.empty()) { | |
| try { | |
| document->removeObject(m_unreferencedBlocksGroup->getNameInDocument()); | |
| } | |
| catch (const Base::Exception& e) { | |
| // It's not critical if removal fails, but we should log it. | |
| e.reportException(); | |
| } | |
| } | |
| else { | |
| m_unreferencedBlocksGroup->Visibility.setValue(false); | |
| } | |
| // If no blocks were defined in the file at all, remove the main definitions | |
| // group as well to keep the document clean. | |
| if (m_blockDefinitionGroup && m_blockDefinitionGroup->Group.getValues().empty()) { | |
| try { | |
| document->removeObject(m_blockDefinitionGroup->getNameInDocument()); | |
| } | |
| catch (const Base::Exception& e) { | |
| e.reportException(); | |
| } | |
| } | |
| // call the base class implementation if it has one | |
| CDxfRead::FinishImport(); | |
| } | |
| bool ImpExpDxfRead::OnReadBlock(const std::string& name, int flags) | |
| { | |
| // Step 1: Check for external references first. This is a critical check. | |
| if ((flags & 0x04) != 0) { // Block is an Xref | |
| UnsupportedFeature("External (xref) BLOCK"); | |
| return SkipBlockContents(); | |
| } | |
| // Step 2: Check if the block is anonymous/system. | |
| bool isAnonymous = (name.find('*') == 0); | |
| if (isAnonymous) { | |
| if (name.size() > 1) { | |
| char type = std::toupper(name[1]); | |
| if (type == 'D') { | |
| m_stats.systemBlockCounts["Dimension-related (*D)"]++; | |
| } | |
| else if (type == 'H' || type == 'X') { | |
| m_stats.systemBlockCounts["Hatch-related (*H, *X)"]++; | |
| } | |
| else { | |
| m_stats.systemBlockCounts["Other System Blocks"]++; | |
| } | |
| } | |
| else { | |
| m_stats.systemBlockCounts["Other System Blocks"]++; | |
| } | |
| if (!m_importHiddenBlocks) { | |
| return SkipBlockContents(); | |
| } | |
| } | |
| else { | |
| m_stats.entityCounts["BLOCK"]++; | |
| } | |
| // Step 3: Check for duplicates to prevent errors. | |
| if (this->Blocks.count(name)) { | |
| ImportError("Duplicate block name '%s' found. Ignoring subsequent definition.", name.c_str()); | |
| return SkipBlockContents(); | |
| } | |
| // Step 4: Use the temporary Block struct and Collector to parse all contents into memory. | |
| // The .emplace method is slightly more efficient here. | |
| auto& temporaryBlock = Blocks.emplace(std::make_pair(name, Block(name, flags))).first->second; | |
| BlockDefinitionCollector blockCollector( | |
| *this, | |
| temporaryBlock.GeometryBuilders, | |
| temporaryBlock.Inserts | |
| ); | |
| if (!ReadBlockContents()) { | |
| return false; // Abort on parsing error | |
| } | |
| // That's it. The block is now parsed into this->Blocks. | |
| // Composition will happen later in ComposeBlocks(). | |
| return true; | |
| } | |
| 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, 1e-8)) { | |
| return; | |
| } | |
| TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(p0, p1).Edge(); | |
| GeometryBuilder builder(edge); | |
| // CORRECTED: Set PrimitiveType conditionally based on m_importMode | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| // For these modes, we want a specific Part primitive (Part::Line) | |
| builder.type = GeometryBuilder::PrimitiveType::Line; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| // For these modes, we want a generic Part::Feature wrapping the TopoDS_Shape. | |
| // PrimitiveType::None will lead to a generic Part::Feature in AddGeometry. | |
| builder.type = GeometryBuilder::PrimitiveType::None; | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start) | |
| { | |
| if (shouldSkipEntity()) { | |
| return; | |
| } | |
| TopoDS_Vertex vertex = BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(); | |
| GeometryBuilder builder(vertex); | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| builder.type = GeometryBuilder::PrimitiveType::Point; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| builder.type = GeometryBuilder::PrimitiveType::None; // Generic Part::Feature | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| 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.Reverse(); | |
| } | |
| gp_Pnt pc = makePoint(center); | |
| gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); | |
| if (circle.Radius() < 1e-9) { | |
| Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n"); | |
| return; | |
| } | |
| TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(); | |
| GeometryBuilder builder(edge); // Instantiate builder once | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| builder.type = GeometryBuilder::PrimitiveType::Arc; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| builder.type = GeometryBuilder::PrimitiveType::None; // Generic Part::Feature | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| 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.Reverse(); | |
| } | |
| gp_Pnt pc = makePoint(center); | |
| gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc)); | |
| if (circle.Radius() < 1e-9) { | |
| Base::Console().warning("ImpExpDxf - ignore degenerate circle\n"); | |
| return; | |
| } | |
| TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circle).Edge(); | |
| GeometryBuilder builder(edge); // Instantiate builder once | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| builder.type = GeometryBuilder::PrimitiveType::Circle; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| builder.type = GeometryBuilder::PrimitiveType::None; // Generic Part::Feature | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| 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<double> 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 | |
| if (shouldSkipEntity()) { | |
| return; | |
| } | |
| 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()) { | |
| TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(geom).Edge(); | |
| GeometryBuilder builder(edge); // Instantiate builder once | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| builder.type = GeometryBuilder::PrimitiveType::Spline; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| builder.type = GeometryBuilder::PrimitiveType::None; // Generic Part::Feature | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| } | |
| 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.Reverse(); | |
| } | |
| 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() < 1e-9) { | |
| Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n"); | |
| return; | |
| } | |
| TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(ellipse).Edge(); | |
| GeometryBuilder builder(edge); // Pass the shape to the builder | |
| switch (m_importMode) { | |
| case ImportMode::EditableDraft: | |
| case ImportMode::EditablePrimitives: | |
| // Tag this geometry so the collector knows to create a Part::Ellipse primitive | |
| builder.type = GeometryBuilder::PrimitiveType::Ellipse; | |
| break; | |
| case ImportMode::IndividualShapes: | |
| case ImportMode::FusedShapes: | |
| default: | |
| // For other modes, create a generic shape (Part:Feature), which is the existing | |
| // behavior. | |
| builder.type = GeometryBuilder::PrimitiveType::None; | |
| break; | |
| } | |
| Collector->AddGeometry(builder); | |
| } | |
| void ImpExpDxfRead::OnReadText( | |
| const Base::Vector3d& point, | |
| const double height, | |
| const std::string& text, | |
| const double rotation | |
| ) | |
| { | |
| if (shouldSkipEntity() || !m_importAnnotations) { | |
| return; | |
| } | |
| auto* p = static_cast<App::FeaturePython*>(document->addObject("App::FeaturePython", "Text")); | |
| if (p) { | |
| p->addDynamicProperty("App::PropertyString", "DxfEntityType", "Internal", "DXF entity type"); | |
| static_cast<App::PropertyString*>(p->getPropertyByName("DxfEntityType"))->setValue("TEXT"); | |
| p->addDynamicProperty("App::PropertyStringList", "Text", "Data", "Text content"); | |
| // Explicitly create the vector to resolve ambiguity | |
| std::vector<std::string> text_values = {text}; | |
| static_cast<App::PropertyStringList*>(p->getPropertyByName("Text"))->setValues(text_values); | |
| p->addDynamicProperty("App::PropertyFloat", "DxfTextHeight", "Internal", "Original text height"); | |
| static_cast<App::PropertyFloat*>(p->getPropertyByName("DxfTextHeight"))->setValue(height); | |
| p->addDynamicProperty("App::PropertyPlacement", "Placement", "Base", "Object placement"); | |
| Base::Placement pl; | |
| pl.setPosition(point); | |
| pl.setRotation(Base::Rotation(Base::Vector3d(0, 0, 1), Base::toRadians(rotation))); | |
| static_cast<App::PropertyPlacement*>(p->getPropertyByName("Placement"))->setValue(pl); | |
| Collector->AddObject(p, "Text"); | |
| } | |
| } | |
| void ImpExpDxfRead::OnReadInsert( | |
| const Base::Vector3d& point, | |
| const Base::Vector3d& scale, | |
| const std::string& name, | |
| double rotation | |
| ) | |
| { | |
| if (shouldSkipEntity()) { | |
| return; | |
| } | |
| // Delegate the action to the currently active collector. | |
| // If the BlockDefinitionCollector is active, it will just store the data. | |
| // If the DrawingEntityCollector is active, it will create the App::Link. | |
| Collector->AddInsert(point, scale, name, rotation); | |
| } | |
| void ImpExpDxfRead::OnReadDimension( | |
| const Base::Vector3d& start, | |
| const Base::Vector3d& end, | |
| const Base::Vector3d& point, | |
| int dimensionType, | |
| double rotation | |
| ) | |
| { | |
| if (shouldSkipEntity() || !m_importAnnotations) { | |
| return; | |
| } | |
| auto* p = static_cast<App::FeaturePython*>(document->addObject("App::FeaturePython", "Dimension")); | |
| if (p) { | |
| p->addDynamicProperty("App::PropertyString", "DxfEntityType", "Internal", "DXF entity type"); | |
| static_cast<App::PropertyString*>(p->getPropertyByName("DxfEntityType"))->setValue("DIMENSION"); | |
| p->addDynamicProperty("App::PropertyVector", "Start", "Data", "Start point of dimension"); | |
| static_cast<App::PropertyVector*>(p->getPropertyByName("Start"))->setValue(start); | |
| p->addDynamicProperty("App::PropertyVector", "End", "Data", "End point of dimension"); | |
| static_cast<App::PropertyVector*>(p->getPropertyByName("End"))->setValue(end); | |
| p->addDynamicProperty("App::PropertyVector", "Dimline", "Data", "Point on dimension line"); | |
| static_cast<App::PropertyVector*>(p->getPropertyByName("Dimline"))->setValue(point); | |
| p->addDynamicProperty( | |
| "App::PropertyInteger", | |
| "DxfDimensionType", | |
| "Internal", | |
| "Original dimension type flag" | |
| ); | |
| static_cast<App::PropertyInteger*>(p->getPropertyByName("DxfDimensionType")) | |
| ->setValue(dimensionType); | |
| p->addDynamicProperty( | |
| "App::PropertyAngle", | |
| "DxfRotation", | |
| "Internal", | |
| "Original dimension rotation" | |
| ); | |
| // rotation is already in radians from the caller | |
| static_cast<App::PropertyAngle*>(p->getPropertyByName("DxfRotation"))->setValue(rotation); | |
| p->addDynamicProperty("App::PropertyPlacement", "Placement", "Base", "Object placement"); | |
| Base::Placement pl; | |
| // Correctly construct the rotation directly from the 4x4 matrix. | |
| // The Base::Rotation constructor will extract the rotational part. | |
| pl.setRotation(Base::Rotation(OCSOrientationTransform)); | |
| static_cast<App::PropertyPlacement*>(p->getPropertyByName("Placement"))->setValue(pl); | |
| Collector->AddObject(p, "Dimension"); | |
| } | |
| } | |
| void ImpExpDxfRead::OnReadPolyline(std::list<VertexInfo>& vertices, int flags) | |
| { | |
| if (shouldSkipEntity()) { | |
| return; | |
| } | |
| if (vertices.size() < 2 && (flags & 1) == 0) { | |
| return; // Not enough vertices for an open polyline | |
| } | |
| TopoDS_Wire wire = BuildWireFromPolyline(vertices, flags); | |
| if (wire.IsNull()) { | |
| return; | |
| } | |
| if (m_importMode == ImportMode::EditableDraft) { | |
| GeometryBuilder builder(wire); | |
| builder.type = GeometryBuilder::PrimitiveType::PolylineFlattened; | |
| Collector->AddGeometry(builder); | |
| } | |
| else if (m_importMode == ImportMode::EditablePrimitives) { | |
| GeometryBuilder builder(wire); | |
| builder.type = GeometryBuilder::PrimitiveType::PolylineParametric; | |
| Collector->AddGeometry(builder); | |
| } | |
| else { | |
| Collector->AddObject(wire, "Polyline"); | |
| } | |
| } | |
| void ImpExpDxfRead::DrawingEntityCollector::AddGeometry(const GeometryBuilder& builder) | |
| { | |
| App::DocumentObject* newDocObj = nullptr; | |
| switch (builder.type) { | |
| case GeometryBuilder::PrimitiveType::Line: { | |
| newDocObj = createLinePrimitive(TopoDS::Edge(builder.shape), Reader.document, "Line"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Circle: { | |
| auto* p = createCirclePrimitive(TopoDS::Edge(builder.shape), Reader.document, "Circle"); | |
| if (p) { | |
| p->Angle1.setValue(0.0); | |
| p->Angle2.setValue(360.0); // Ensure it's a full circle if it's a circle entity | |
| } | |
| newDocObj = p; | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Arc: { | |
| newDocObj = createCirclePrimitive(TopoDS::Edge(builder.shape), Reader.document, "Arc"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Point: { | |
| newDocObj = createVertexPrimitive(TopoDS::Vertex(builder.shape), Reader.document, "Point"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Ellipse: { | |
| newDocObj = createEllipsePrimitive(TopoDS::Edge(builder.shape), Reader.document, "Ellipse"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::Spline: { | |
| newDocObj = createGenericShapeFeature(builder.shape, Reader.document, "Spline"); | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::PolylineFlattened: { | |
| Reader.CreateFlattenedPolyline(TopoDS::Wire(builder.shape), "Polyline"); | |
| newDocObj = nullptr; // Object handled by helper | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::PolylineParametric: { | |
| Reader.CreateParametricPolyline(TopoDS::Wire(builder.shape), "Polyline"); | |
| newDocObj = nullptr; // Object handled by helper | |
| break; | |
| } | |
| case GeometryBuilder::PrimitiveType::None: // Fallback for generic shapes (e.g., 3DFACE) | |
| default: { | |
| newDocObj = createGenericShapeFeature(builder.shape, Reader.document, "Shape"); | |
| break; | |
| } | |
| } | |
| // Common post-creation steps for objects NOT handled by helper functions | |
| if (newDocObj) { | |
| Reader.IncrementCreatedObjectCount(); | |
| Reader._addOriginalLayerProperty(newDocObj); | |
| Reader.MoveToLayer(newDocObj); | |
| Reader.ApplyGuiStyles(static_cast<Part::Feature*>(newDocObj)); | |
| } | |
| } | |
| 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::PropertyLinkListHidden*>( | |
| (((App::FeaturePythonPyT<App::DocumentObjectPy>*)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) { | |
| // Get the correct boolean value based on the user's preference. | |
| PyObject* overrideValue = m_preserveColors ? Py_True : Py_False; | |
| PyObject_SetAttrString(result->DraftLayerView, "OverrideLineColorChildren", overrideValue); | |
| PyObject_SetAttrString( | |
| result->DraftLayerView, | |
| "OverrideShapeAppearanceChildren", | |
| overrideValue | |
| ); | |
| } | |
| // 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<Layer*>(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::_addOriginalLayerProperty(App::DocumentObject* obj) | |
| { | |
| if (obj && m_entityAttributes.m_Layer) { | |
| obj->addDynamicProperty( | |
| "App::PropertyString", | |
| "OriginalLayer", | |
| "Internal", | |
| "Layer name from the original DXF file.", | |
| App::Property::Hidden | |
| ); | |
| static_cast<App::PropertyString*>(obj->getPropertyByName("OriginalLayer")) | |
| ->setValue(m_entityAttributes.m_Layer->Name.c_str()); | |
| } | |
| } | |
| void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape, const char* nameBase) | |
| { | |
| auto pcFeature = Reader.document->addObject<Part::Feature>(nameBase); | |
| if (pcFeature) { | |
| Reader.IncrementCreatedObjectCount(); | |
| pcFeature->Shape.setValue(shape); | |
| Reader._addOriginalLayerProperty(pcFeature); | |
| Reader.MoveToLayer(pcFeature); | |
| Reader.ApplyGuiStyles(pcFeature); | |
| } | |
| } | |
| void ImpExpDxfRead::DrawingEntityCollector::AddObject(App::DocumentObject* obj, const char* /*nameBase*/) | |
| { | |
| Reader.MoveToLayer(obj); | |
| Reader._addOriginalLayerProperty(obj); | |
| // Safely apply styles by checking the object's actual type (only for objects not replaced | |
| // by Python) | |
| if (auto feature = dynamic_cast<Part::Feature*>(obj)) { | |
| Reader.ApplyGuiStyles(feature); | |
| } | |
| else if (auto pyFeature = dynamic_cast<App::FeaturePython*>(obj)) { | |
| Reader.ApplyGuiStyles(pyFeature); | |
| } | |
| else if (auto link = dynamic_cast<App::Link*>(obj)) { | |
| Reader.ApplyGuiStyles(link); | |
| } | |
| } | |
| void ImpExpDxfRead::DrawingEntityCollector::AddObject(FeaturePythonBuilder shapeBuilder) | |
| { | |
| Reader.IncrementCreatedObjectCount(); | |
| App::FeaturePython* shape = shapeBuilder(Reader.OCSOrientationTransform); | |
| if (shape != nullptr) { | |
| Reader._addOriginalLayerProperty(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/Draft"); | |
| 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<int>(adapt.GetType()) | |
| ); | |
| } | |
| } | |
| if (optionExpPoints) { | |
| TopExp_Explorer verts(input, TopAbs_VERTEX); | |
| std::vector<gp_Pnt> 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<gp_Pnt> 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); | |
| } | |
| // Helper function to discretize a curve into polyline vertices | |
| // Returns true if discretization was successful and pd was populated | |
| bool ImpExpDxfWrite::discretizeCurveToPolyline(BRepAdaptor_Curve& c, LWPolyDataOut& pd) const | |
| { | |
| 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); | |
| if (!discretizer.IsDone() || discretizer.NbPoints() <= 0) { | |
| return false; | |
| } | |
| int nbPoints = discretizer.NbPoints(); | |
| // for closed curves, don't include the last point if it duplicates the first | |
| int endIndex = nbPoints; | |
| if (pd.Flag && nbPoints > 1) { | |
| gp_Pnt pFirst = c.Value(discretizer.Parameter(1)); | |
| gp_Pnt pLast = c.Value(discretizer.Parameter(nbPoints)); | |
| if (pFirst.Distance(pLast) < Precision::Confusion()) { | |
| endIndex = nbPoints - 1; | |
| } | |
| } | |
| for (int i = 1; i <= endIndex; i++) { | |
| gp_Pnt p = c.Value(discretizer.Parameter(i)); | |
| pd.Verts.push_back(gPntTopoint3D(p)); | |
| } | |
| pd.nVert = static_cast<int>(pd.Verts.size()); | |
| return true; | |
| } | |
| void ImpExpDxfWrite::exportLWPoly(BRepAdaptor_Curve& c) | |
| { | |
| LWPolyDataOut pd; | |
| if (discretizeCurveToPolyline(c, pd)) { | |
| writeLWPolyLine(pd); | |
| } | |
| } | |
| void ImpExpDxfWrite::exportPolyline(BRepAdaptor_Curve& c) | |
| { | |
| LWPolyDataOut pd; | |
| if (discretizeCurveToPolyline(c, pd)) { | |
| 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); | |
| } | |
| Py::Object ImpExpDxfRead::getStatsAsPyObject() | |
| { | |
| // Create a Python dictionary to hold all import statistics. | |
| Py::Dict statsDict; | |
| // Populate the dictionary with general information about the import. | |
| statsDict.setItem("dxfVersion", Py::String(m_stats.dxfVersion)); | |
| statsDict.setItem("dxfEncoding", Py::String(m_stats.dxfEncoding)); | |
| statsDict.setItem("scalingSource", Py::String(m_stats.scalingSource)); | |
| statsDict.setItem("fileUnits", Py::String(m_stats.fileUnits)); | |
| statsDict.setItem("finalScalingFactor", Py::Float(m_stats.finalScalingFactor)); | |
| statsDict.setItem("importTimeSeconds", Py::Float(m_stats.importTimeSeconds)); | |
| statsDict.setItem("totalEntitiesCreated", Py::Long(m_stats.totalEntitiesCreated)); | |
| // Create a nested dictionary for the counts of each DXF entity type read. | |
| Py::Dict entityCountsDict; | |
| for (const auto& pair : m_stats.entityCounts) { | |
| entityCountsDict.setItem(pair.first.c_str(), Py::Long(pair.second)); | |
| } | |
| statsDict.setItem("entityCounts", entityCountsDict); | |
| // Create a nested dictionary for the import settings used for this session. | |
| Py::Dict importSettingsDict; | |
| for (const auto& pair : m_stats.importSettings) { | |
| importSettingsDict.setItem(pair.first.c_str(), Py::String(pair.second)); | |
| } | |
| statsDict.setItem("importSettings", importSettingsDict); | |
| // Create a nested dictionary for any unsupported DXF features encountered. | |
| Py::Dict unsupportedFeaturesDict; | |
| for (const auto& pair : m_stats.unsupportedFeatures) { | |
| Py::List occurrencesList; | |
| for (const auto& occurrence : pair.second) { | |
| Py::Tuple infoTuple(2); | |
| infoTuple.setItem(0, Py::Long(occurrence.first)); | |
| infoTuple.setItem(1, Py::String(occurrence.second)); | |
| occurrencesList.append(infoTuple); | |
| } | |
| unsupportedFeaturesDict.setItem(pair.first.c_str(), occurrencesList); | |
| } | |
| statsDict.setItem("unsupportedFeatures", unsupportedFeaturesDict); | |
| // Create a nested dictionary for the counts of system blocks encountered. | |
| Py::Dict systemBlockCountsDict; | |
| for (const auto& pair : m_stats.systemBlockCounts) { | |
| systemBlockCountsDict.setItem(pair.first.c_str(), Py::Long(pair.second)); | |
| } | |
| statsDict.setItem("systemBlockCounts", systemBlockCountsDict); | |
| // Return the fully populated statistics dictionary to the Python caller. | |
| return statsDict; | |
| } | |