Handle all combos of "group into blocks" "use DXF colors" "use layers"
Fixes #11873 this was the primary goal of these changes Fixes (partially) #11874 the parts of a polyline are combined as a compound object (shape) but it would be preferable for them to be made into a wire (if possible) Fixes #11872 Objects in a block definition are now kept separately until the block is inserted, in which case the inserted objects are subject to all the other options regarding combining. Fixes (partially, review required) #11871 Text and dimensions are now kept as part of the block definition and are placed in the drawing when the block is inserted but this code has not been extensively tested. Affects #11875, custom types are not made, but the labels now reflect the object types rather than all being "Shapennn" This leaves the importer options handling in a bit of a mess that needs cleanup eventually, but this can be a new issue. This includes some importer flags that have no corresponding options (e.g. import frozen layers), some flags not yet implemented, some flags not yet even declared in the code because their implementation is further off (import hatch outlines), and some suggested future options (import SOLIDs as faces) Centralize the calculation of the OCS for entities as they're read from the DXF. Most of the entities don't use this yet, but some of them roll their own crude Normal Vector handling. Because the new code takes priority over the old for reading the normal vector, such code will always see (0, 0, 1) as the extrusion direction.
This commit is contained in:
committed by
Yorik van Havre
parent
c57a83cb4f
commit
0dcfe94505
@@ -31,6 +31,7 @@
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <BRepBuilderAPI_Transform.hxx>
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <GCPnts_UniformAbscissa.hxx>
|
||||
#include <GeomAPI_Interpolate.hxx>
|
||||
@@ -81,20 +82,128 @@ using BRepAdaptor_HCurve = BRepAdaptor_Curve;
|
||||
ImpExpDxfRead::ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc)
|
||||
: CDxfRead(filepath)
|
||||
, document(pcDoc)
|
||||
, optionGroupLayers(false)
|
||||
, optionImportAnnotations(true)
|
||||
{
|
||||
setOptionSource("User parameter:BaseApp/Preferences/Mod/Draft");
|
||||
setOptions();
|
||||
}
|
||||
|
||||
bool ImpExpDxfRead::ReadEntitiesSection()
|
||||
{
|
||||
DrawingEntityCollector collector(*this);
|
||||
if (m_mergeOption < SingleShapes) {
|
||||
std::map<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, "Compound");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!CDxfRead::ReadEntitiesSection()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_preserveLayers) {
|
||||
// Hide the Hidden layers if possible (if GUI exists)
|
||||
// We do this now rather than when the layer is created so all objects
|
||||
// within the layers also become hidden.
|
||||
for (auto& layerEntry : Layers) {
|
||||
auto layer = (Layer*)layerEntry.second;
|
||||
if (layer->DraftLayerView != nullptr && layer->Hidden) {
|
||||
PyObject_CallMethod(layer->DraftLayerView, "hide", nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImpExpDxfRead::CombineShapes(std::list<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);
|
||||
}
|
||||
}
|
||||
|
||||
void ImpExpDxfRead::setOptions()
|
||||
{
|
||||
ParameterGrp::handle hGrp =
|
||||
App::GetApplication().GetParameterGroupByPath(getOptionSource().c_str());
|
||||
optionGroupLayers = hGrp->GetBool("groupLayers", false);
|
||||
optionImportAnnotations = hGrp->GetBool("dxftext", false);
|
||||
m_preserveLayers = hGrp->GetBool("dxfUseDraftVisGroups", true);
|
||||
m_preserveColors = hGrp->GetBool("dxfGetOriginalColors", true);
|
||||
// Default for creation type is to create draft objects.
|
||||
// The radio-button structure of the options dialog should generally prevent this condition.
|
||||
m_mergeOption = DraftObjects;
|
||||
if (hGrp->GetBool("groupLayers", true)) {
|
||||
// Group all compatible objects together
|
||||
m_mergeOption = MergeShapes;
|
||||
}
|
||||
else if (hGrp->GetBool("dxfCreatePart", true)) {
|
||||
// Create (non-draft) Shape objects when possible
|
||||
m_mergeOption = SingleShapes;
|
||||
}
|
||||
else if (hGrp->GetBool("dxfCreateDraft", true)) {
|
||||
// Create only Draft objects, making the result closest to drawn-from-scratch
|
||||
m_mergeOption = DraftObjects;
|
||||
}
|
||||
// TODO: joingeometry should give an intermediate between MergeShapes and SingleShapes which
|
||||
// will merge shapes that happen to join end-to-end. As such it should be in the radio button
|
||||
// set, except that the legacy importer can do joining either for sketches or for shapes. What
|
||||
// this really means is there should be an "Import as sketch" checkbox, and only the
|
||||
// MergeShapes, JoinShapes, and SingleShapes radio buttons should be allowed, i.e. Draft Objects
|
||||
// would be ignored.
|
||||
SetAdditionalScaling(hGrp->GetFloat("dxfScaling", 1.0));
|
||||
|
||||
m_importAnnotations = hGrp->GetBool("dxftext", false);
|
||||
m_importPoints = hGrp->GetBool("dxfImportPoints", true);
|
||||
m_importPaperSpaceEntities = hGrp->GetBool("dxflayout", false);
|
||||
m_importHiddenBlocks = hGrp->GetBool("dxfstarblocks", false);
|
||||
// TODO: There is currently no option for this: m_importFrozenLayers =
|
||||
// hGrp->GetBool("dxffrozenLayers", false);
|
||||
// TODO: There is currently no option for this: m_importHiddenLayers =
|
||||
// hGrp->GetBool("dxfhiddenLayers", true);
|
||||
}
|
||||
|
||||
bool ImpExpDxfRead::OnReadBlock(const std::string& name, int flags)
|
||||
{
|
||||
if ((flags & 0x04) != 0) {
|
||||
// Note that this doesn't mean there are not entities in the block. I don't
|
||||
// know if the external reference can be cached because there are two other bits
|
||||
// here, 0x10 and 0x20, that seem to handle "resolved" external references.
|
||||
UnsupportedFeature("External (xref) BLOCK");
|
||||
}
|
||||
else if (!m_importHiddenBlocks && (flags & 0x01) != 0) {
|
||||
// It is an anonymous block used to build dimensions, hatches, etc so we don't need it
|
||||
// and don't want to be complaining about unhandled entity types.
|
||||
// Note that if it *is* for a hatch we could actually import it and use it to draw a hatch.
|
||||
}
|
||||
else if (Blocks.count(name) > 0) {
|
||||
ImportError("Duplicate block name '%s'\n", name);
|
||||
}
|
||||
else {
|
||||
Block& block = Blocks.insert(std::make_pair(name, Block(name, flags))).first->second;
|
||||
BlockDefinitionCollector blockCollector(*this,
|
||||
block.Shapes,
|
||||
block.FeatureBuildersList,
|
||||
block.Inserts);
|
||||
return ReadBlockContents();
|
||||
}
|
||||
return SkipBlockContents();
|
||||
}
|
||||
|
||||
void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start,
|
||||
@@ -107,17 +216,13 @@ void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start,
|
||||
// TODO: Really?? What about the people designing integrated circuits?
|
||||
return;
|
||||
}
|
||||
BRepBuilderAPI_MakeEdge makeEdge(p0, p1);
|
||||
TopoDS_Edge edge = makeEdge.Edge();
|
||||
AddObject(new Part::TopoShape(edge));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line");
|
||||
}
|
||||
|
||||
|
||||
void ImpExpDxfRead::OnReadPoint(const Base::Vector3d& start)
|
||||
{
|
||||
BRepBuilderAPI_MakeVertex makeVertex(makePoint(start));
|
||||
TopoDS_Vertex vertex = makeVertex.Vertex();
|
||||
AddObject(new Part::TopoShape(vertex));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point");
|
||||
}
|
||||
|
||||
|
||||
@@ -136,9 +241,7 @@ void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start,
|
||||
gp_Pnt pc = makePoint(center);
|
||||
gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc));
|
||||
if (circle.Radius() > 0) {
|
||||
BRepBuilderAPI_MakeEdge makeEdge(circle, p0, p1);
|
||||
TopoDS_Edge edge = makeEdge.Edge();
|
||||
AddObject(new Part::TopoShape(edge));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc");
|
||||
}
|
||||
else {
|
||||
Base::Console().Warning("ImpExpDxf - ignore degenerate arc of circle\n");
|
||||
@@ -159,9 +262,7 @@ void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start,
|
||||
gp_Pnt pc = makePoint(center);
|
||||
gp_Circ circle(gp_Ax2(pc, up), p0.Distance(pc));
|
||||
if (circle.Radius() > 0) {
|
||||
BRepBuilderAPI_MakeEdge makeEdge(circle);
|
||||
TopoDS_Edge edge = makeEdge.Edge();
|
||||
AddObject(new Part::TopoShape(edge));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle");
|
||||
}
|
||||
else {
|
||||
Base::Console().Warning("ImpExpDxf - ignore degenerate circle\n");
|
||||
@@ -278,9 +379,7 @@ void ImpExpDxfRead::OnReadSpline(struct SplineData& sd)
|
||||
throw Standard_Failure();
|
||||
}
|
||||
|
||||
BRepBuilderAPI_MakeEdge makeEdge(geom);
|
||||
TopoDS_Edge edge = makeEdge.Edge();
|
||||
AddObject(new Part::TopoShape(edge));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline");
|
||||
}
|
||||
catch (const Standard_Failure&) {
|
||||
Base::Console().Warning("ImpExpDxf - failed to create bspline\n");
|
||||
@@ -305,9 +404,7 @@ void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center,
|
||||
gp_Elips ellipse(gp_Ax2(pc, up), major_radius, minor_radius);
|
||||
ellipse.Rotate(gp_Ax1(pc, up), rotation);
|
||||
if (ellipse.MinorRadius() > 0) {
|
||||
BRepBuilderAPI_MakeEdge makeEdge(ellipse);
|
||||
TopoDS_Edge edge = makeEdge.Edge();
|
||||
AddObject(new Part::TopoShape(edge));
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse");
|
||||
}
|
||||
else {
|
||||
Base::Console().Warning("ImpExpDxf - ignore degenerate ellipse\n");
|
||||
@@ -322,13 +419,15 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point,
|
||||
{
|
||||
// Note that our parameters do not contain all the information needed to properly orient the
|
||||
// text. As a result the text will always appear on the XY plane
|
||||
if (optionImportAnnotations) {
|
||||
if (LayerName().rfind("BLOCKS", 0) != 0) {
|
||||
PyObject* draftModule = nullptr;
|
||||
Base::Rotation rot(Base::Vector3d(0, 0, 1), rotation);
|
||||
PyObject* placement = new Base::PlacementPy(Base::Placement(point, rot));
|
||||
draftModule = PyImport_ImportModule("Draft");
|
||||
if (m_importAnnotations) {
|
||||
auto makeText = [=](const Base::Matrix4D& transform) -> App::FeaturePython* {
|
||||
PyObject* draftModule = getDraftModule();
|
||||
if (draftModule != nullptr) {
|
||||
Base::Matrix4D localTransform;
|
||||
localTransform.rotZ(rotation);
|
||||
localTransform.move(point);
|
||||
PyObject* placement =
|
||||
new Base::PlacementPy(Base::Placement(transform * localTransform));
|
||||
// returns a wrapped App::FeaturePython
|
||||
auto builtText = dynamic_cast<App::FeaturePythonPyT<App::DocumentObjectPy>*>(
|
||||
// NOLINTNEXTLINE(readability/nolint)
|
||||
@@ -340,18 +439,14 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point,
|
||||
placement,
|
||||
0,
|
||||
height));
|
||||
Py_DECREF(placement);
|
||||
if (builtText != nullptr) {
|
||||
ApplyGuiStyles(
|
||||
dynamic_cast<App::FeaturePython*>(builtText->getDocumentObjectPtr()));
|
||||
return dynamic_cast<App::FeaturePython*>(builtText->getDocumentObjectPtr());
|
||||
}
|
||||
}
|
||||
// We own all the return values so we must release them.
|
||||
Py_DECREF(placement);
|
||||
Py_XDECREF(draftModule);
|
||||
}
|
||||
else {
|
||||
UnsupportedFeature("TEXT or MTEXT in BLOCK definition");
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
Collector->AddObject((FeaturePythonBuilder)makeText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,37 +456,78 @@ void ImpExpDxfRead::OnReadInsert(const Base::Vector3d& point,
|
||||
const std::string& name,
|
||||
double rotation)
|
||||
{
|
||||
// std::cout << "Inserting block " << name << " rotation " << rotation << " pos " << point[0] <<
|
||||
// "," << point[1] << "," << point[2] << " scale " << scale[0] << "," << scale[1] << "," <<
|
||||
// scale[2] << std::endl;
|
||||
std::string prefix = "BLOCKS ";
|
||||
prefix += name;
|
||||
prefix += " ";
|
||||
auto checkScale = [=](double scale) {
|
||||
return scale != 0.0 ? scale : 1.0;
|
||||
};
|
||||
for (const auto& layer : layers) {
|
||||
if (layer.first.substr(0, prefix.size()) == prefix) {
|
||||
BRep_Builder builder;
|
||||
TopoDS_Compound comp;
|
||||
builder.MakeCompound(comp);
|
||||
for (const auto& shape : layer.second) {
|
||||
const TopoDS_Shape& sh = shape->getShape();
|
||||
if (!sh.IsNull()) {
|
||||
builder.Add(comp, sh);
|
||||
}
|
||||
}
|
||||
if (!comp.IsNull()) {
|
||||
auto pcomp = new Part::TopoShape(comp);
|
||||
Base::Matrix4D mat;
|
||||
mat.scale(checkScale(scale[0]), checkScale(scale[1]), checkScale(scale[2]));
|
||||
mat.rotZ(rotation);
|
||||
mat.move(point[0], point[1], point[2]);
|
||||
pcomp->transformShape(mat, true);
|
||||
AddObject(pcomp);
|
||||
Collector->AddInsert(point, scale, name, rotation);
|
||||
}
|
||||
void ImpExpDxfRead::ExpandInsert(const std::string& name,
|
||||
const Base::Matrix4D& transform,
|
||||
const Base::Vector3d& point,
|
||||
double rotation,
|
||||
const Base::Vector3d& scale)
|
||||
{
|
||||
if (Blocks.count(name) == 0) {
|
||||
ImportError("Reference to undefined or external block '%s'\n", name);
|
||||
return;
|
||||
}
|
||||
Block& block = Blocks.at(name);
|
||||
// Apply the scaling, rotation, and move before the OCSEnttityTransform and place the result io
|
||||
// BaseEntityTransform,
|
||||
Base::Matrix4D localTransform;
|
||||
localTransform.scale(scale.x, scale.y, scale.z);
|
||||
localTransform.rotZ(rotation);
|
||||
localTransform.move(point[0], point[1], point[2]);
|
||||
localTransform = transform * localTransform;
|
||||
CommonEntityAttributes mainAttributes = m_entityAttributes;
|
||||
for (const auto& [attributes, shapes] : block.Shapes) {
|
||||
// Put attributes into m_entityAttributes after using the latter to set byblock values in
|
||||
// the former.
|
||||
m_entityAttributes = attributes;
|
||||
m_entityAttributes.ResolveByBlockAttributes(mainAttributes);
|
||||
|
||||
for (const TopoDS_Shape& shape : shapes) {
|
||||
// TODO???: See the comment in TopoShape::makeTransform regarding calling
|
||||
// Moved(identityTransform) on the new shape
|
||||
Collector->AddObject(
|
||||
BRepBuilderAPI_Transform(shape,
|
||||
Part::TopoShape::convert(localTransform),
|
||||
Standard_True)
|
||||
.Shape(),
|
||||
"InsertPart"); // TODO: The collection should contain the nameBase to use
|
||||
}
|
||||
}
|
||||
for (const auto& [attributes, featureBuilders] : block.FeatureBuildersList) {
|
||||
// Put attributes into m_entityAttributes after using the latter to set byblock values in
|
||||
// the former.
|
||||
m_entityAttributes = attributes;
|
||||
m_entityAttributes.ResolveByBlockAttributes(mainAttributes);
|
||||
|
||||
for (const FeaturePythonBuilder& featureBuilder : featureBuilders) {
|
||||
// TODO: Any non-identity transform from the original entity record needs to be applied
|
||||
// before OCSEntityTransform (which includes this INSERT's transform followed by the
|
||||
// transform for the INSERT's context (i.e. from an outeer INSERT)
|
||||
// TODO: Perhaps pass a prefix ("Insert") to the builder to make the object name so
|
||||
// Draft objects in a block get named similarly to Shapes.
|
||||
App::FeaturePython* feature = featureBuilder(localTransform);
|
||||
if (feature != nullptr) {
|
||||
// Note that the featureBuilder has already placed this object in the drawing as a
|
||||
// top-level object, so we don't have to add them but we must place it in its layer
|
||||
// and set its gui styles
|
||||
MoveToLayer(feature);
|
||||
ApplyGuiStyles(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [attributes, inserts] : block.Inserts) {
|
||||
// Put attributes into m_entityAttributes after using the latter to set byblock values in
|
||||
// the former.
|
||||
m_entityAttributes = attributes;
|
||||
m_entityAttributes.ResolveByBlockAttributes(mainAttributes);
|
||||
|
||||
for (const Block::Insert& insert : inserts) {
|
||||
// TODO: Apply the OCSOrientationTransform saved with the Insert statement to
|
||||
// localTransform. (pass localTransform*insert.OCSDirectionTransform)
|
||||
ExpandInsert(insert.Name, localTransform, insert.Point, insert.Rotation, insert.Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -400,53 +536,160 @@ void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start,
|
||||
const Base::Vector3d& point,
|
||||
double /*rotation*/)
|
||||
{
|
||||
if (optionImportAnnotations) {
|
||||
PyObject* draftModule = nullptr;
|
||||
PyObject* startPy = new Base::VectorPy(start);
|
||||
PyObject* endPy = new Base::VectorPy(end);
|
||||
PyObject* lineLocationPy = new Base::VectorPy(point);
|
||||
draftModule = PyImport_ImportModule("Draft");
|
||||
if (draftModule != nullptr) {
|
||||
// returns a wrapped App::FeaturePython
|
||||
auto builtDim = dynamic_cast<App::FeaturePythonPyT<App::DocumentObjectPy>*>(
|
||||
// NOLINTNEXTLINE(readability/nolint)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
(Base::PyObjectBase*)PyObject_CallMethod(draftModule,
|
||||
"make_linear_dimension",
|
||||
"OOO",
|
||||
startPy,
|
||||
endPy,
|
||||
lineLocationPy));
|
||||
if (builtDim != nullptr) {
|
||||
ApplyGuiStyles(dynamic_cast<App::FeaturePython*>(builtDim->getDocumentObjectPtr()));
|
||||
if (m_importAnnotations) {
|
||||
auto makeDimension = [=](const Base::Matrix4D& transform) -> App::FeaturePython* {
|
||||
PyObject* draftModule = getDraftModule();
|
||||
if (draftModule != nullptr) {
|
||||
// TODO: Capture and apply OCSOrientationTransform to OCS coordinates
|
||||
// Note, some of the locations in the DXF are OCS and some are UCS, but UCS doesn't
|
||||
// mean UCS when in a block expansion, it means 'transform'
|
||||
// So we want transform*vector for "UCS" coordinates and transform*ocdCapture*vector
|
||||
// for "OCS" coordinates
|
||||
//
|
||||
// We implement the transform by mapping all the points from OCS to UCS
|
||||
// TODO: Set the Normal property to transform*(0,0,1,0)
|
||||
// TODO: Set the Direction property to transform*(the desired direction).
|
||||
// By default this is parallel to (start-end).
|
||||
PyObject* startPy = new Base::VectorPy(transform * start);
|
||||
PyObject* endPy = new Base::VectorPy(transform * end);
|
||||
PyObject* lineLocationPy = new Base::VectorPy(transform * point);
|
||||
// returns a wrapped App::FeaturePython
|
||||
auto builtDim = dynamic_cast<App::FeaturePythonPyT<App::DocumentObjectPy>*>(
|
||||
// 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<App::FeaturePython*>(builtDim->getDocumentObjectPtr());
|
||||
}
|
||||
}
|
||||
}
|
||||
// We own all the return values so we must release them.
|
||||
Py_DECREF(startPy);
|
||||
Py_DECREF(endPy);
|
||||
Py_DECREF(lineLocationPy);
|
||||
Py_XDECREF(draftModule);
|
||||
return nullptr;
|
||||
};
|
||||
Collector->AddObject((FeaturePythonBuilder)makeDimension);
|
||||
}
|
||||
}
|
||||
void ImpExpDxfRead::OnReadPolyline(std::list<VertexInfo>& vertices, int flags)
|
||||
{
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>> ShapesToCombine;
|
||||
{
|
||||
// TODO: Currently ExpandPolyline calls OnReadArc etc to generate the pieces, and these
|
||||
// create TopoShape objects which ShapeSavingEntityCollector can gather up.
|
||||
// Eventually when m_mergeOption being DraftObjects is implemented OnReadArc etc might
|
||||
// generate Draft objects which ShapeSavingEntityCollector does not save.
|
||||
// We need either a collector that collects everything (and we have to figure out
|
||||
// how to join Draft objects) or we need to temporarily set m_mergeOption to SingleShapes
|
||||
// if it is set to DraftObjects (and safely restore it on exceptions)
|
||||
// A clean way would be to give the collector a "makeDraftObjects" property,
|
||||
// and our special collector could give this the value 'false' whereas the main
|
||||
// collector would base this on the option setting.
|
||||
// Also ShapeSavingEntityCollector classifies by entityAttributes which is not needed here
|
||||
// because they are constant throughout.
|
||||
ShapeSavingEntityCollector savingCollector(*this, ShapesToCombine);
|
||||
ExplodePolyline(vertices, flags);
|
||||
}
|
||||
// Join the shapes.
|
||||
if (!ShapesToCombine.empty()) {
|
||||
// TODO: If we want Draft objects and all segments are straight lines we can make a draft
|
||||
// wire.
|
||||
CombineShapes(ShapesToCombine.begin()->second, "Polyline");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ImpExpDxfRead::AddObject(Part::TopoShape* shape)
|
||||
ImpExpDxfRead::Layer::Layer(const std::string& name,
|
||||
ColorIndex_t color,
|
||||
std::string&& lineType,
|
||||
PyObject* drawingLayer)
|
||||
: CDxfRead::Layer(name, color, std::move(lineType))
|
||||
, DraftLayer(drawingLayer)
|
||||
, DraftLayerView(drawingLayer == nullptr ? nullptr
|
||||
: PyObject_GetAttrString(drawingLayer, "ViewObject"))
|
||||
{}
|
||||
ImpExpDxfRead::Layer::~Layer()
|
||||
{
|
||||
std::vector<Part::TopoShape*> vec;
|
||||
std::string destinationLayerName(LayerName());
|
||||
if (layers.count(destinationLayerName) != 0) {
|
||||
vec = layers[destinationLayerName];
|
||||
Py_XDECREF(DraftLayer);
|
||||
Py_XDECREF(DraftLayerView);
|
||||
}
|
||||
|
||||
CDxfRead::Layer*
|
||||
ImpExpDxfRead::MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType)
|
||||
{
|
||||
if (m_preserveLayers) {
|
||||
// Hidden layers are implemented in the wrapup code after the entire file has been read.
|
||||
App::Color appColor = ObjectColor(color);
|
||||
PyObject* draftModule = nullptr;
|
||||
PyObject* layer = nullptr;
|
||||
draftModule = PyImport_ImportModule("Draft");
|
||||
if (draftModule != nullptr) {
|
||||
// After the colours, I also want to pass the draw_style, but there is an intervening
|
||||
// line-width parameter. It is easier to just pass that parameter's default value than
|
||||
// to do the handstands to pass a named parameter.
|
||||
// TODO: Pass the appropriate draw_style (from "Solid" "Dashed" "Dotted" "DashDot")
|
||||
// This needs an ObjectDrawStyleName analogous to ObjectColor but at the ImpExpDxfGui
|
||||
// level.
|
||||
layer =
|
||||
// NOLINTNEXTLINE(readability/nolint)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
(Base::PyObjectBase*)PyObject_CallMethod(draftModule,
|
||||
"make_layer",
|
||||
"s(fff)(fff)fs",
|
||||
name.c_str(),
|
||||
appColor.r,
|
||||
appColor.g,
|
||||
appColor.b,
|
||||
appColor.r,
|
||||
appColor.g,
|
||||
appColor.b,
|
||||
2.0,
|
||||
"Solid");
|
||||
Py_DECREF(draftModule);
|
||||
}
|
||||
auto result = new Layer(name, color, std::move(lineType), layer);
|
||||
if (result->DraftLayerView != nullptr) {
|
||||
PyObject_SetAttrString(result->DraftLayerView, "OverrideLineColorChildren", Py_False);
|
||||
PyObject_SetAttrString(result->DraftLayerView, "OverrideShapeColorChildren", Py_False);
|
||||
}
|
||||
|
||||
// We make our own layer class even if we could not make a layer. MoveToLayer will ignore
|
||||
// such layers but we have to do this because it is not a polymorphic type so we can't tell
|
||||
// what we pull out of m_entityAttributes.m_Layer.
|
||||
return result;
|
||||
}
|
||||
vec.push_back(shape);
|
||||
layers[destinationLayerName] = vec;
|
||||
if (!optionGroupLayers) {
|
||||
if (destinationLayerName.rfind("BLOCKS", 0) != 0) {
|
||||
auto pcFeature =
|
||||
dynamic_cast<Part::Feature*>(document->addObject("Part::Feature", "Shape"));
|
||||
pcFeature->Shape.setValue(shape->getShape());
|
||||
ApplyGuiStyles(pcFeature);
|
||||
return CDxfRead::MakeLayer(name, color, std::move(lineType));
|
||||
}
|
||||
void ImpExpDxfRead::MoveToLayer(App::DocumentObject* object) const
|
||||
{
|
||||
if (m_preserveLayers) {
|
||||
static PyObject* addObjectName =
|
||||
PyUnicode_FromString("addObject"); // This never gets freed, we always have a reference
|
||||
auto layer = static_cast<const Layer*>(m_entityAttributes.m_Layer);
|
||||
if (layer->DraftLayer != nullptr) {
|
||||
// We have to move the object to layer->DraftLayer
|
||||
// The DraftLayer will have a Proxy attribute which has a addObject attribute which we
|
||||
// call with (draftLayer, draftObject) Checking from python, the layer is a
|
||||
// App::FeaturePython, and its Proxy is a draftobjects.layer.Layer
|
||||
PyObject* proxy = PyObject_GetAttrString(layer->DraftLayer, "Proxy");
|
||||
if (proxy != nullptr) {
|
||||
// TODO: De we have to check if the method exists? The legacy importer does.
|
||||
PyObject_CallMethodObjArgs(proxy,
|
||||
addObjectName,
|
||||
layer->DraftLayer,
|
||||
object->getPyObject(),
|
||||
nullptr);
|
||||
Py_DECREF(proxy);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: else Hide the object if it is in a Hidden layer? That won't work because we've cleared
|
||||
// out m_entityAttributes.m_Layer
|
||||
}
|
||||
|
||||
|
||||
@@ -487,32 +730,21 @@ std::string ImpExpDxfRead::Deformat(const char* text)
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
void ImpExpDxfRead::AddGraphics() const
|
||||
void ImpExpDxfRead::DrawingEntityCollector::AddObject(const TopoDS_Shape& shape,
|
||||
const char* nameBase)
|
||||
{
|
||||
if (optionGroupLayers) {
|
||||
for (const auto& layer : layers) {
|
||||
BRep_Builder builder;
|
||||
TopoDS_Compound comp;
|
||||
builder.MakeCompound(comp);
|
||||
std::string k = layer.first;
|
||||
if (k == "0") { // FreeCAD doesn't like an object name being '0'...
|
||||
k = "LAYER_0";
|
||||
}
|
||||
if (k.rfind("BLOCKS", 0) != 0) {
|
||||
for (const auto& shape : layer.second) {
|
||||
const TopoDS_Shape& sh = shape->getShape();
|
||||
if (!sh.IsNull()) {
|
||||
builder.Add(comp, sh);
|
||||
}
|
||||
}
|
||||
if (!comp.IsNull()) {
|
||||
auto pcFeature = dynamic_cast<Part::Feature*>(
|
||||
document->addObject("Part::Feature", k.c_str()));
|
||||
pcFeature->Shape.setValue(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto pcFeature =
|
||||
dynamic_cast<Part::Feature*>(Reader.document->addObject("Part::Feature", nameBase));
|
||||
pcFeature->Shape.setValue(shape);
|
||||
Reader.MoveToLayer(pcFeature);
|
||||
Reader.ApplyGuiStyles(pcFeature);
|
||||
}
|
||||
void ImpExpDxfRead::DrawingEntityCollector::AddObject(FeaturePythonBuilder shapeBuilder)
|
||||
{
|
||||
App::FeaturePython* shape = shapeBuilder(Reader.OCSOrientationTransform);
|
||||
if (shape != nullptr) {
|
||||
Reader.MoveToLayer(shape);
|
||||
Reader.ApplyGuiStyles(shape);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <gp_Pnt.hxx>
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <Mod/Part/App/TopoShape.h>
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
|
||||
@@ -40,8 +41,19 @@ class ImportExport ImpExpDxfRead: public CDxfRead
|
||||
{
|
||||
public:
|
||||
ImpExpDxfRead(const std::string& filepath, App::Document* pcDoc);
|
||||
ImpExpDxfRead(const ImpExpDxfRead&) = delete;
|
||||
ImpExpDxfRead(ImpExpDxfRead&&) = delete;
|
||||
void operator=(const ImpExpDxfRead&) = delete;
|
||||
void operator=(ImpExpDxfRead&&) = delete;
|
||||
~ImpExpDxfRead() override
|
||||
{
|
||||
Py_XDECREF(DraftModule);
|
||||
}
|
||||
|
||||
bool ReadEntitiesSection() override;
|
||||
|
||||
// CDxfRead's virtual functions
|
||||
bool OnReadBlock(const std::string& name, int flags) override;
|
||||
void OnReadLine(const Base::Vector3d& start, const Base::Vector3d& end, bool hidden) override;
|
||||
void OnReadPoint(const Base::Vector3d& start) override;
|
||||
void OnReadText(const Base::Vector3d& point,
|
||||
@@ -69,14 +81,20 @@ public:
|
||||
const Base::Vector3d& scale,
|
||||
const std::string& name,
|
||||
double rotation) override;
|
||||
// Expand a block reference; this should only happen when the collector draws to the document
|
||||
// rather than saving things The transform should include the OCS Orientation transform for the
|
||||
// insertion.
|
||||
void ExpandInsert(const std::string& name,
|
||||
const Base::Matrix4D& transform,
|
||||
const Base::Vector3d& point,
|
||||
double rotation,
|
||||
const Base::Vector3d& scale);
|
||||
void OnReadDimension(const Base::Vector3d& start,
|
||||
const Base::Vector3d& end,
|
||||
const Base::Vector3d& point,
|
||||
double rotation) override;
|
||||
void AddGraphics() const override;
|
||||
void OnReadPolyline(std::list<VertexInfo>& /*vertices*/, int flags) override;
|
||||
|
||||
// FreeCAD-specific functions
|
||||
void AddObject(Part::TopoShape* shape); // Called by OnRead functions to add Part objects
|
||||
std::string Deformat(const char* text); // Removes DXF formatting from texts
|
||||
|
||||
std::string getOptionSource()
|
||||
@@ -94,17 +112,235 @@ private:
|
||||
{
|
||||
return {point3d.x, point3d.y, point3d.z};
|
||||
}
|
||||
void MoveToLayer(App::DocumentObject* object) const;
|
||||
// Combine all the shapes in the given shapes collection into a single shape, and AddObject that
|
||||
// to the drawing. unref's all the shapes in the collection, possibly freeing them.
|
||||
void CombineShapes(std::list<TopoDS_Shape>& shapes, const char* nameBase) const;
|
||||
PyObject* DraftModule = nullptr;
|
||||
|
||||
protected:
|
||||
virtual void ApplyGuiStyles(Part::Feature* /*object*/)
|
||||
{}
|
||||
virtual void ApplyGuiStyles(App::FeaturePython* /*object*/)
|
||||
{}
|
||||
PyObject* getDraftModule()
|
||||
{
|
||||
if (DraftModule == nullptr) {
|
||||
DraftModule = PyImport_ImportModule("Draft");
|
||||
}
|
||||
return DraftModule;
|
||||
}
|
||||
CDxfRead::Layer*
|
||||
MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType) override;
|
||||
|
||||
// Overrides for layer management so we can record the layer objects in the FreeCAD drawing that
|
||||
// are associated with the layers in the DXF.
|
||||
class Layer: public CDxfRead::Layer
|
||||
{
|
||||
public:
|
||||
Layer(const std::string& name,
|
||||
ColorIndex_t color,
|
||||
std::string&& lineType,
|
||||
PyObject* drawingLayer);
|
||||
Layer(const Layer&) = delete;
|
||||
Layer(Layer&&) = delete;
|
||||
void operator=(const Layer&) = delete;
|
||||
void operator=(Layer&&) = delete;
|
||||
~Layer() override;
|
||||
PyObject* const DraftLayer;
|
||||
PyObject* const DraftLayerView;
|
||||
};
|
||||
|
||||
using FeaturePythonBuilder =
|
||||
std::function<App::FeaturePython*(const Base::Matrix4D& transform)>;
|
||||
// Block management
|
||||
class Block
|
||||
{
|
||||
public:
|
||||
struct Insert
|
||||
{
|
||||
const Base::Vector3d Point;
|
||||
const Base::Vector3d Scale;
|
||||
const std::string Name;
|
||||
const double Rotation;
|
||||
|
||||
// NOLINTNEXTLINE(readability/nolint)
|
||||
// NOLINTNEXTLINE(modernize-pass-by-value) Pass by value adds unwarranted complexity
|
||||
Insert(const std::string& Name,
|
||||
const Base::Vector3d& Point,
|
||||
double Rotation,
|
||||
const Base::Vector3d& Scale)
|
||||
: Point(Point)
|
||||
, Scale(Scale)
|
||||
, Name(Name)
|
||||
, Rotation(Rotation)
|
||||
{}
|
||||
};
|
||||
// NOLINTNEXTLINE(readability/nolint)
|
||||
// NOLINTNEXTLINE(modernize-pass-by-value) Pass by value adds unwarranted complexity
|
||||
Block(const std::string& name, int flags)
|
||||
: Name(name)
|
||||
, Flags(flags)
|
||||
{}
|
||||
const std::string Name;
|
||||
const int Flags;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>> Shapes;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<FeaturePythonBuilder>>
|
||||
FeatureBuildersList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Insert>> Inserts;
|
||||
};
|
||||
|
||||
private:
|
||||
std::map<std::string, Block> Blocks;
|
||||
App::Document* document;
|
||||
bool optionGroupLayers;
|
||||
bool optionImportAnnotations;
|
||||
std::map<std::string, std::vector<Part::TopoShape*>> layers;
|
||||
std::string m_optionSource;
|
||||
|
||||
protected:
|
||||
virtual void ApplyGuiStyles(Part::Feature* /*object*/) const
|
||||
{}
|
||||
virtual void ApplyGuiStyles(App::FeaturePython* /*object*/) const
|
||||
{}
|
||||
|
||||
// Gathering of created entities
|
||||
class EntityCollector
|
||||
{
|
||||
public:
|
||||
explicit EntityCollector(ImpExpDxfRead& reader)
|
||||
: Reader(reader)
|
||||
, previousCollector(reader.Collector)
|
||||
{
|
||||
Reader.Collector = this;
|
||||
}
|
||||
EntityCollector(const EntityCollector&) = delete;
|
||||
EntityCollector(EntityCollector&&) = delete;
|
||||
void operator=(const EntityCollector&) = delete;
|
||||
void operator=(EntityCollector&&) = delete;
|
||||
|
||||
virtual ~EntityCollector()
|
||||
{
|
||||
if (Reader.Collector == this) {
|
||||
Reader.Collector = previousCollector;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by OnReadXxxx functions to add Part objects
|
||||
virtual void AddObject(const TopoDS_Shape& shape, const char* nameBase) = 0;
|
||||
// Called by OnReadXxxx functions to add FeaturePython (draft) objects.
|
||||
// Because we can't readily copy Draft objects, this method instead takes a builder which,
|
||||
// when called, creates and returns the object.
|
||||
virtual void AddObject(FeaturePythonBuilder shapeBuilder) = 0;
|
||||
// Called by OnReadInsert to either remember in a nested block or expand the block into the
|
||||
// drawing
|
||||
virtual void AddInsert(const Base::Vector3d& point,
|
||||
const Base::Vector3d& scale,
|
||||
const std::string& name,
|
||||
double rotation) = 0;
|
||||
|
||||
protected:
|
||||
ImpExpDxfRead& Reader;
|
||||
|
||||
private:
|
||||
EntityCollector* const previousCollector;
|
||||
};
|
||||
class DrawingEntityCollector: public EntityCollector
|
||||
{
|
||||
// This collector places all objects into the drawing
|
||||
public:
|
||||
explicit DrawingEntityCollector(ImpExpDxfRead& reader)
|
||||
: EntityCollector(reader)
|
||||
{}
|
||||
|
||||
void AddObject(const TopoDS_Shape& shape, const char* nameBase) override;
|
||||
void AddObject(FeaturePythonBuilder shapeBuilder) override;
|
||||
void AddInsert(const Base::Vector3d& point,
|
||||
const Base::Vector3d& scale,
|
||||
const std::string& name,
|
||||
double rotation) override
|
||||
{
|
||||
Reader.ExpandInsert(name, Reader.OCSOrientationTransform, point, rotation, scale);
|
||||
}
|
||||
};
|
||||
class ShapeSavingEntityCollector: public DrawingEntityCollector
|
||||
{
|
||||
// This places draft objects into the drawing but stashes away Shapes.
|
||||
public:
|
||||
ShapeSavingEntityCollector(
|
||||
ImpExpDxfRead& reader,
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>>& shapesList)
|
||||
: DrawingEntityCollector(reader)
|
||||
, ShapesList(shapesList)
|
||||
{}
|
||||
|
||||
void AddObject(const TopoDS_Shape& shape, const char* /*nameBase*/) override
|
||||
{
|
||||
ShapesList[Reader.m_entityAttributes].push_back(shape);
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>>& ShapesList;
|
||||
};
|
||||
#ifdef LATER
|
||||
class PolylineEntityCollector: public CombiningDrawingEntityCollector
|
||||
{
|
||||
// This places draft objects into the drawing but stashes away Shapes.
|
||||
public:
|
||||
explicit PolylineEntityCollector(CDxfRead& reader)
|
||||
: CombiningDrawingEntityCollector(reader)
|
||||
, previousMmergeOption(reader.m_mergeOption)
|
||||
{
|
||||
// TODO: We also have to temporarily shift from DraftObjects to some other mode so the
|
||||
// pieces of the polyline some through as shapes and not Draft objects, or maybe the
|
||||
// polyline builder should not call OnArcRead and OnLineRead to do their dirty work.
|
||||
}
|
||||
~PolylineEntityCollector();
|
||||
|
||||
void AddObject(Part::TopoShape* shape) override;
|
||||
void AddObject(App::FeaturePython* shape) override;
|
||||
|
||||
private:
|
||||
const EntityCollector* previousEntityCollector;
|
||||
const eEntityMergeType_t previousMmergeOption;
|
||||
};
|
||||
#endif
|
||||
class BlockDefinitionCollector: public EntityCollector
|
||||
{
|
||||
// Collect all the entities plus their entityAttrubutes into given collections.
|
||||
public:
|
||||
BlockDefinitionCollector(
|
||||
ImpExpDxfRead& reader,
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>>& shapesList,
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<FeaturePythonBuilder>>&
|
||||
featureBuildersList,
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Block::Insert>>& insertsList)
|
||||
: EntityCollector(reader)
|
||||
, ShapesList(shapesList)
|
||||
, FeatureBuildersList(featureBuildersList)
|
||||
, InsertsList(insertsList)
|
||||
{}
|
||||
|
||||
// TODO: We will want AddAttributeDefinition as well.
|
||||
void AddObject(const TopoDS_Shape& shape, const char* /*nameBase*/) override
|
||||
{
|
||||
ShapesList[Reader.m_entityAttributes].push_back(shape);
|
||||
}
|
||||
void AddObject(FeaturePythonBuilder shapeBuilder) override
|
||||
{
|
||||
FeatureBuildersList[Reader.m_entityAttributes].push_back(shapeBuilder);
|
||||
}
|
||||
void AddInsert(const Base::Vector3d& point,
|
||||
const Base::Vector3d& scale,
|
||||
const std::string& name,
|
||||
double rotation) override
|
||||
{
|
||||
InsertsList[Reader.m_entityAttributes].push_back(
|
||||
Block::Insert(name, point, rotation, scale));
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>>& ShapesList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<FeaturePythonBuilder>>&
|
||||
FeatureBuildersList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Block::Insert>>& InsertsList;
|
||||
};
|
||||
|
||||
private:
|
||||
EntityCollector* Collector = nullptr;
|
||||
};
|
||||
|
||||
class ImportExport ImpExpDxfWrite: public CDxfWrite
|
||||
@@ -122,7 +358,7 @@ public:
|
||||
{
|
||||
return m_optionSource;
|
||||
}
|
||||
void setOptionSource(std::string s)
|
||||
void setOptionSource(const std::string& s)
|
||||
{
|
||||
m_optionSource = s;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// dxf.cpp
|
||||
// dxf.cpp
|
||||
// Copyright (c) 2009, Dan Heeks
|
||||
// This program is released under the BSD license. See the file COPYING for details.
|
||||
// modified 2018 wandererfan
|
||||
@@ -1813,8 +1813,16 @@ CDxfRead::CDxfRead(const std::string& filepath)
|
||||
CDxfRead::~CDxfRead()
|
||||
{
|
||||
delete m_ifs;
|
||||
// Delete the Layer objects which are referenced by pointer from the Layers table.
|
||||
for (auto& pair : Layers) {
|
||||
delete pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Static member initializers
|
||||
const std::string CDxfRead::LineTypeByLayer("BYLAYER"); // NOLINT(runtime/string)
|
||||
const std::string CDxfRead::LineTypeByBlock("BYBLOCK"); // NOLINT(runtime/string)
|
||||
const std::string CDxfRead::DefaultLineType("CONTINUOUS"); // NOLINT(runtime/string)
|
||||
|
||||
//
|
||||
// Setup for ProcessCommonEntityAttribute
|
||||
@@ -1842,6 +1850,12 @@ void CDxfRead::SetupScaledDoubleIntoList(eDXFGroupCode_t x_record_type, list<dou
|
||||
m_coordinate_attributes.emplace(x_record_type,
|
||||
std::pair(&ProcessScaledDoubleIntoList, &destination));
|
||||
}
|
||||
void CDxfRead::Setup3DDirectionAttribute(eDXFGroupCode_t x_record_type, Base::Vector3d& destination)
|
||||
{
|
||||
SetupValueAttribute((eDXFGroupCode_t)(x_record_type + eXOffset), destination.x);
|
||||
SetupValueAttribute((eDXFGroupCode_t)(x_record_type + eYOffset), destination.y);
|
||||
SetupValueAttribute((eDXFGroupCode_t)(x_record_type + eZOffset), destination.z);
|
||||
}
|
||||
void CDxfRead::SetupStringAttribute(eDXFGroupCode_t x_record_type, std::string& destination)
|
||||
{
|
||||
m_coordinate_attributes.emplace(x_record_type, std::pair(&ProcessStdString, &destination));
|
||||
@@ -1908,16 +1922,6 @@ void CDxfRead::InitializeAttributes()
|
||||
{
|
||||
m_coordinate_attributes.clear();
|
||||
}
|
||||
void CDxfRead::InitializeCommonEntityAttributes()
|
||||
{
|
||||
InitializeAttributes();
|
||||
m_layer_name.clear();
|
||||
m_LineType.clear();
|
||||
m_ColorIndex = ColorBylayer;
|
||||
SetupStringAttribute(eLinetypeName, m_LineType);
|
||||
SetupStringAttribute(eLayerName, m_layer_name);
|
||||
SetupValueAttribute(eColor, m_ColorIndex);
|
||||
}
|
||||
bool CDxfRead::ProcessAttribute()
|
||||
{
|
||||
auto found = m_coordinate_attributes.find(m_record_type);
|
||||
@@ -1934,6 +1938,59 @@ void CDxfRead::ProcessAllAttributes()
|
||||
}
|
||||
repeat_last_record();
|
||||
}
|
||||
void CDxfRead::ProcessAllEntityAttributes()
|
||||
{
|
||||
ProcessAllAttributes();
|
||||
ResolveEntityAttributes();
|
||||
}
|
||||
void CDxfRead::ResolveEntityAttributes()
|
||||
{
|
||||
m_entityAttributes.ResolveBylayerAttributes(*this);
|
||||
// TODO: Look at the space and layer (hidden/frozen?) and options and return false if the entity
|
||||
// is not needed.
|
||||
// TODO: INSERT must not call this because an INSERT on a hidden layer should always be
|
||||
// honoured.
|
||||
|
||||
// Calculate the net entity transformation.
|
||||
|
||||
// This is to handle the Object Coordinate System used in many DXF records. Note that versions
|
||||
// before R13 used the term ECS (Entity Coordinate System) instead. Here's who uses OCS: Lines
|
||||
// and Points use WCS except they can be extruded (have nonzero Thickness (39)) which occurs in
|
||||
// the OCS Z direction all 3D objects use WCS entirely Dimensions use a mix of OCS and WCS,
|
||||
// Circle, Arc, Dolid, Trace, Text, Attib, Attdef, Shape, Insert, (lw)Polyline/Vertex, hatch,
|
||||
// image all use the OCS
|
||||
//
|
||||
// The transformed Z axis is in EntityNormalVector, but we rescale it in case the DXF contains
|
||||
// an unnormalized value
|
||||
if (EntityNormalVector.IsNull()) {
|
||||
ImportError("Entity has zero-length extrusion direction\n");
|
||||
}
|
||||
EntityNormalVector.Normalize();
|
||||
// Apply the Arbitrary Axis Algorithm to determine the X and Y directions
|
||||
// The purpose of this algorithm is to calculate a conventional 3d orientation based only on a Z
|
||||
// direction, while avoiding taking the cross product of two vectors that are nearly parallel,
|
||||
// which would be subject to a lot of numerical inaccuracy. In this case, "close to" the Z axis
|
||||
// means the X and Y components of EntityNormalVector are less than 1/64, a value chosen because
|
||||
// it is exactly representable in all binary floating-point systems.
|
||||
Base::Vector3d xDirection;
|
||||
if (EntityNormalVector.x < ArbitraryAxisAlgorithmThreshold
|
||||
&& EntityNormalVector.y < ArbitraryAxisAlgorithmThreshold) {
|
||||
// The Z axis is close to the UCS Z axis, the X direction is UCSY × OCSZ
|
||||
static const Base::Vector3d UCSYAxis(0, 1, 0);
|
||||
xDirection = UCSYAxis % EntityNormalVector;
|
||||
}
|
||||
else {
|
||||
// otherwise, the X direction is UCSZ × OCSZ
|
||||
static const Base::Vector3d UCSZAxis(0, 0, 1);
|
||||
xDirection = UCSZAxis % EntityNormalVector;
|
||||
}
|
||||
OCSOrientationTransform.setCol(0, xDirection);
|
||||
// In all cases the Y direction is the Zdirection × XDirection which gives a right-hand
|
||||
// orthonormal coordinate system
|
||||
OCSOrientationTransform.setCol(1, EntityNormalVector % xDirection);
|
||||
// and EntityNormalVector is of course the direction of the Z axis in the UCS.
|
||||
OCSOrientationTransform.setCol(2, EntityNormalVector);
|
||||
}
|
||||
|
||||
//
|
||||
// The individual Entity reader functions
|
||||
@@ -1944,7 +2001,7 @@ bool CDxfRead::ReadLine()
|
||||
Base::Vector3d end;
|
||||
Setup3DVectorAttribute(ePrimaryPoint, start);
|
||||
Setup3DVectorAttribute(ePoint2, end);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadLine(start, end, LineTypeIsHidden());
|
||||
return true;
|
||||
@@ -1954,7 +2011,7 @@ bool CDxfRead::ReadPoint()
|
||||
{
|
||||
Base::Vector3d location;
|
||||
Setup3DVectorAttribute(ePrimaryPoint, location);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadPoint(location);
|
||||
return true;
|
||||
@@ -1973,7 +2030,7 @@ bool CDxfRead::ReadArc()
|
||||
SetupValueAttribute(eAngleDegrees1, start_angle_degrees);
|
||||
SetupValueAttribute(eAngleDegrees2, end_angle_degrees);
|
||||
Setup3DVectorAttribute(eExtrusionDirection, extrusionDirection);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadArc(start_angle_degrees,
|
||||
end_angle_degrees,
|
||||
@@ -2005,7 +2062,7 @@ bool CDxfRead::ReadSpline()
|
||||
Setup3DCoordinatesIntoLists(ePoint2, sd.fitx, sd.fity, sd.fitz);
|
||||
Setup3DCoordinatesIntoLists(ePoint3, sd.starttanx, sd.starttany, sd.starttanz);
|
||||
Setup3DCoordinatesIntoLists(ePoint4, sd.endtanx, sd.endtany, sd.endtanz);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadSpline(sd);
|
||||
return true;
|
||||
@@ -2018,7 +2075,7 @@ bool CDxfRead::ReadCircle()
|
||||
|
||||
Setup3DVectorAttribute(ePrimaryPoint, centre);
|
||||
SetupScaledDoubleAttribute(eFloat1, radius);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadCircle(centre, radius, LineTypeIsHidden());
|
||||
return true;
|
||||
@@ -2054,6 +2111,7 @@ bool CDxfRead::ReadText()
|
||||
}
|
||||
}
|
||||
}
|
||||
ResolveEntityAttributes();
|
||||
|
||||
if ((this->*stringToUTF8)(textPrefix)) {
|
||||
OnReadText(insertionPoint, height * 25.4 / 72.0, textPrefix, rotation);
|
||||
@@ -2079,7 +2137,7 @@ bool CDxfRead::ReadEllipse()
|
||||
SetupValueAttribute(eFloat1, eccentricity);
|
||||
SetupValueAttribute(eFloat2, startAngleRadians);
|
||||
SetupValueAttribute(eFloat3, endAngleRadians);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
OnReadEllipse(centre, majorAxisEnd, eccentricity, startAngleRadians, endAngleRadians);
|
||||
return true;
|
||||
@@ -2130,6 +2188,8 @@ bool CDxfRead::ReadLwPolyLine()
|
||||
vertices.push_back(currentVertex);
|
||||
}
|
||||
|
||||
ResolveEntityAttributes();
|
||||
|
||||
OnReadPolyline(vertices, flags);
|
||||
repeat_last_record();
|
||||
return true;
|
||||
@@ -2142,7 +2202,7 @@ bool CDxfRead::ReadPolyLine()
|
||||
int flags = 0;
|
||||
|
||||
SetupValueAttribute(eInteger1, flags);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
// We are now followed by a series of VERTEX entities followed by ENDSEQ.
|
||||
// To avoid eating and discarding the rest of the entieies if ENDSEQ is missing,
|
||||
@@ -2153,7 +2213,7 @@ bool CDxfRead::ReadPolyLine()
|
||||
// Set vertex defaults
|
||||
currentVertex.location = Base::Vector3d();
|
||||
currentVertex.bulge = 0.0;
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
vertices.push_back(currentVertex);
|
||||
}
|
||||
if (!IsObjectName("SEQEND")) {
|
||||
@@ -2178,7 +2238,7 @@ bool CDxfRead::ReadInsert()
|
||||
SetupValueAttribute(eFloat4, scale.z);
|
||||
SetupValueAttribute(eAngleDegrees1, rotationDegrees);
|
||||
SetupStringAttribute(eName, blockName);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
OnReadInsert(center, scale, blockName, Base::toRadians(rotationDegrees));
|
||||
return (true);
|
||||
}
|
||||
@@ -2206,7 +2266,7 @@ bool CDxfRead::ReadDimension()
|
||||
Setup3DVectorAttribute(ePoint2, textPosition); // OCS
|
||||
SetupValueAttribute(eAngleDegrees1, rotation);
|
||||
SetupValueAttribute(eInteger1, dimensionType);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
|
||||
dimensionType &= eTypeMask; // Remove flags
|
||||
switch ((eDimensionType_t)dimensionType) {
|
||||
@@ -2224,41 +2284,27 @@ bool CDxfRead::ReadDimension()
|
||||
bool CDxfRead::ReadUnknownEntity()
|
||||
{
|
||||
UnsupportedFeature("Entity type '%s'", m_record_data);
|
||||
ProcessAllAttributes();
|
||||
ProcessAllEntityAttributes();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDxfRead::ReadBlockInfo()
|
||||
{
|
||||
int blockType = 0;
|
||||
std::string blockName;
|
||||
InitializeAttributes();
|
||||
// Both 2 and 3 are the block name.
|
||||
SetupStringAttribute(eName, m_block_name);
|
||||
SetupStringAttribute(eExtraText, m_block_name);
|
||||
SetupStringAttribute(eName, blockName);
|
||||
SetupStringAttribute(eExtraText, blockName);
|
||||
SetupValueAttribute(eInteger1, blockType);
|
||||
ProcessAllAttributes();
|
||||
|
||||
// Read the entities in the block definition.
|
||||
// These are processed just like in-drawing entities but the code can
|
||||
// recognize that we are in the BLOCKS section, and which block we are defining,
|
||||
// by looking at the result of LayerName()
|
||||
// Note that the Layer Name in the block entities is ignored even though it
|
||||
// should be used to resolve BYLAYER attributes and also for placing the entity
|
||||
// when the block is inserted.
|
||||
if ((blockType & 0x04) != 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");
|
||||
}
|
||||
while (get_next_record() && m_record_type != eObjectType && !IsObjectName("ENDBLK")) {
|
||||
if ((blockType & 0x01) != 0) {
|
||||
// It is an anonymous block used to build dimensions, hatches, etc so we don't need it
|
||||
// and don't want to complaining about unhandled entity types.
|
||||
while (get_next_record() && m_record_type != eObjectType) {}
|
||||
repeat_last_record();
|
||||
}
|
||||
else if (IgnoreErrors()) {
|
||||
return OnReadBlock(blockName, blockType);
|
||||
}
|
||||
bool CDxfRead::ReadBlockContents()
|
||||
{
|
||||
while (get_next_record() && m_record_type == eObjectType && !IsObjectName("ENDBLK")) {
|
||||
if (IgnoreErrors()) {
|
||||
try {
|
||||
if (!ReadEntity()) {
|
||||
return false;
|
||||
@@ -2275,6 +2321,22 @@ bool CDxfRead::ReadBlockInfo()
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool CDxfRead::SkipBlockContents()
|
||||
{
|
||||
while (get_next_record() && m_record_type == eObjectType && !IsObjectName("ENDBLK")) {
|
||||
if (IgnoreErrors()) {
|
||||
try {
|
||||
ProcessAllAttributes();
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
ProcessAllAttributes();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename... args>
|
||||
void CDxfRead::UnsupportedFeature(const char* format, args&&... argValuess)
|
||||
@@ -2336,7 +2398,7 @@ void CDxfRead::repeat_last_record()
|
||||
//
|
||||
// Intercepts for On... calls to derived class
|
||||
// (These have distinct signatures from the ones they call)
|
||||
bool CDxfRead::OnReadPolyline(std::list<VertexInfo>& vertices, int flags)
|
||||
bool CDxfRead::ExplodePolyline(std::list<VertexInfo>& vertices, int flags)
|
||||
{
|
||||
if (vertices.size() < 2) {
|
||||
// TODO: Warning
|
||||
@@ -2372,6 +2434,8 @@ bool CDxfRead::OnReadPolyline(std::list<VertexInfo>& vertices, int flags)
|
||||
OnReadLine(startVertex->location, endVertex->location, false);
|
||||
}
|
||||
}
|
||||
// elsethis is the first loop iteration on an open shape, endVertex is the first point, and
|
||||
// there is no closure line to draw engin there.
|
||||
startVertex = endVertex;
|
||||
}
|
||||
return true;
|
||||
@@ -2564,6 +2628,7 @@ void CDxfRead::DoRead(const bool ignore_errors /* = false */)
|
||||
return;
|
||||
}
|
||||
|
||||
StartImport();
|
||||
// Loop reading the sections.
|
||||
while (get_next_record()) {
|
||||
if (m_record_type != eObjectType) {
|
||||
@@ -2583,7 +2648,7 @@ void CDxfRead::DoRead(const bool ignore_errors /* = false */)
|
||||
return;
|
||||
}
|
||||
}
|
||||
AddGraphics();
|
||||
FinishImport();
|
||||
|
||||
// FLush out any unsupported features messages
|
||||
if (!m_unsupportedFeaturesNoted.empty()) {
|
||||
@@ -2621,10 +2686,32 @@ bool CDxfRead::ReadSection()
|
||||
}
|
||||
return ReadIgnoredSection();
|
||||
}
|
||||
|
||||
void CDxfRead::ProcessLayerReference(CDxfRead* object, void* target)
|
||||
{
|
||||
if (object->Layers.count(object->m_record_data) == 0) {
|
||||
object->ImportError("First reference to missing Layer '%s'", object->m_record_data);
|
||||
// Synthesize the Layer so we don't get the same error again.
|
||||
// We need to take copies of the string arguments because MakeLayer uses them as move
|
||||
// inputs.
|
||||
object->Layers[object->m_record_data] =
|
||||
object->MakeLayer(object->m_record_data, DefaultColor, std::string(DefaultLineType));
|
||||
}
|
||||
*static_cast<Layer**>(target) = object->Layers.at(object->m_record_data);
|
||||
}
|
||||
bool CDxfRead::ReadEntity()
|
||||
{
|
||||
InitializeCommonEntityAttributes();
|
||||
InitializeAttributes();
|
||||
m_entityAttributes.SetDefaults();
|
||||
EntityNormalVector.Set(0, 0, 1);
|
||||
Setup3DVectorAttribute(eExtrusionDirection, EntityNormalVector);
|
||||
SetupStringAttribute(eLinetypeName, m_entityAttributes.m_LineType);
|
||||
m_coordinate_attributes.emplace(eLayerName,
|
||||
std::pair(&ProcessLayerReference, &m_entityAttributes.m_Layer));
|
||||
SetupValueAttribute(
|
||||
eCoordinateSpace,
|
||||
m_entityAttributes.m_paperSpace); // TODO: Ensure the stream is noboolalpha (for that
|
||||
// matter ensure the stream has the "C" locale
|
||||
SetupValueAttribute(eColor, m_entityAttributes.m_Color);
|
||||
// The entity record is already the current record and is already checked as a type 0 record
|
||||
if (IsObjectName("LINE")) {
|
||||
return ReadLine();
|
||||
@@ -2800,7 +2887,6 @@ bool CDxfRead::ReadBlocksSection()
|
||||
if (!ReadBlockInfo()) {
|
||||
ImportError("CDxfRead::DoRead() Failed to read block\n");
|
||||
}
|
||||
m_block_name.clear();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2840,13 +2926,15 @@ bool CDxfRead::ReadEntitiesSection()
|
||||
bool CDxfRead::ReadLayer()
|
||||
{
|
||||
std::string layername;
|
||||
ColorIndex_t layerColor = 0;
|
||||
ColorIndex_t layerColor = DefaultColor;
|
||||
int layerFlags = 0;
|
||||
std::string lineTypeName(DefaultLineType);
|
||||
InitializeAttributes();
|
||||
|
||||
SetupStringAttribute(eName, layername);
|
||||
SetupValueAttribute(eColor, layerColor);
|
||||
SetupValueAttribute(eInteger1, layerFlags);
|
||||
SetupStringAttribute(eLinetypeName, lineTypeName);
|
||||
ProcessAllAttributes();
|
||||
if (layername.empty()) {
|
||||
ImportError("CDxfRead::ReadLayer() - no layer name\n");
|
||||
@@ -2859,11 +2947,16 @@ bool CDxfRead::ReadLayer()
|
||||
}
|
||||
if (layerColor < 0) {
|
||||
UnsupportedFeature("Hidden layers");
|
||||
layerColor = -layerColor;
|
||||
}
|
||||
m_layer_ColorIndex_map[layername] = layerColor;
|
||||
Layers[layername] = MakeLayer(layername, layerColor, std::move(lineTypeName));
|
||||
return true;
|
||||
}
|
||||
CDxfRead::Layer*
|
||||
CDxfRead::MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType)
|
||||
{
|
||||
return new Layer(name, color, std::move(lineType));
|
||||
}
|
||||
|
||||
bool CDxfRead::ReadLayerTable()
|
||||
{
|
||||
// Read to the next TABLE record indicating another table in the TABLES section, or to the
|
||||
@@ -2902,24 +2995,6 @@ bool CDxfRead::ReadIgnoredTable()
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string CDxfRead::LayerName() const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (!m_block_name.empty()) {
|
||||
result.append("BLOCKS ");
|
||||
result.append(m_block_name);
|
||||
result.append(" ");
|
||||
}
|
||||
|
||||
else if (!m_layer_name.empty()) {
|
||||
result.append("ENTITIES ");
|
||||
result.append(m_layer_name);
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers)
|
||||
inline static double level(int distance, double blackLevel)
|
||||
{
|
||||
@@ -2951,20 +3026,8 @@ inline static App::Color wheel(int hue, double blackLevel, double multiplier = 1
|
||||
(float)(level(hue - 8, blackLevel) * multiplier),
|
||||
(float)(level(hue - 16, blackLevel) * multiplier));
|
||||
}
|
||||
App::Color CDxfRead::ObjectColor() const
|
||||
App::Color CDxfRead::ObjectColor(ColorIndex_t index)
|
||||
{
|
||||
int index = m_ColorIndex;
|
||||
if (index == ColorBylayer) // if color = layer color, replace by color from layer
|
||||
{
|
||||
// We could just use [] to find the color except this will make an entry with value 0 if
|
||||
// there is none (rather than just returning 0 on each such call), and this my cause a
|
||||
// problem if we try to evaluate color reading BLOCK definitions, since these appear before
|
||||
// the LAYER table. Note that we shouldn't be evaluating color during block definition but
|
||||
// this might be happening already.
|
||||
index = m_layer_ColorIndex_map.count(m_layer_name) > 0
|
||||
? m_layer_ColorIndex_map.at(m_layer_name)
|
||||
: 0;
|
||||
}
|
||||
// TODO: If it is ColorByBlock we need to use the color of the INSERT entity.
|
||||
// This is tricky because a block can itself contain INSERT entities and we don't currently
|
||||
// record the required information. IIRC INSERT in a block will do something strange like
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <Base/Matrix.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/Console.h>
|
||||
#include <App/Color.h>
|
||||
@@ -460,25 +461,107 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
// The following are options which can be altered before DoRead is called.
|
||||
|
||||
// An additional scaling factor to apply after any scaling implied by the DXF file header
|
||||
// (dxfScaling)
|
||||
double m_additionalScaling = 1.0;
|
||||
|
||||
// The following provide a state when reading any entity: If m_block_name is not empty the
|
||||
// entity is in a BLOCK being defined, and LayerName() will return "BLOCKS xxxx" where xxxx is
|
||||
// the block name. Otherwise m_layer_name will be the layer name from the entity records
|
||||
// (default to "0") and LayerName() will return "ENTITIES xxxx" where xxxx is the layer name.
|
||||
// This is clunky but it is a non-private interface and so difficult to change.
|
||||
std::string m_layer_name;
|
||||
std::string m_block_name;
|
||||
// This group of options control which attributes are preserved on import
|
||||
|
||||
protected:
|
||||
// NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
// Place entities in drawing layers as coded in the DXF file.
|
||||
// Otherwise place objects as top-level drawing objects (but still honour BYLAYER attributes).
|
||||
// (dxfUseDraftVisGroups)
|
||||
bool m_preserveLayers = true;
|
||||
|
||||
// Colour entities as coded in the DXF file.
|
||||
// Otherwise leave everything as the default FreeCAD colours.
|
||||
// (dxfGetOriginalColors)
|
||||
bool m_preserveColors = true;
|
||||
// Control object simplification (preserve types and individual identities)
|
||||
using eEntityMergeType_t = enum {
|
||||
// Merge shapes (lines, arcs, circles, etc) into a single compound object when attributes
|
||||
// match
|
||||
MergeShapes,
|
||||
// Make individual dearing shapes for each DXF entity
|
||||
SingleShapes,
|
||||
// Make appropriately-types Draft objects for each DXF entity, giving superior property
|
||||
// access.
|
||||
DraftObjects
|
||||
};
|
||||
// This is a combination of ui options:
|
||||
// "Group layers into blocks" (groupLayers) checkbox which means MergeShapes,
|
||||
// "simple part shapes" (dxfCreatePart) radio button which means SingleShapes
|
||||
// "Draft objects" (dxfCreateDraft) radio button which means DraftObjects
|
||||
// We do not support (dxfCreateSketch).
|
||||
// (joingeometry) is described as being slow so I think the implication is that it only joins
|
||||
// linear entities that meet end-to-end to form them into wire objects. As such it is
|
||||
// intermediate between MergeShapes and SingleShapes and is not implemented.
|
||||
// TODO: The options structure allows conflicting settings for these options because
|
||||
// (groupLayers) and (joingeometry) are separate options, not part of the radio button set. The
|
||||
// others are excluded from having several set by the UI structure but code can set multiple
|
||||
// conflicting values.
|
||||
eEntityMergeType_t m_mergeOption = DraftObjects;
|
||||
|
||||
// This group of options controls the import of certain types of entities or entities having
|
||||
// certain properties
|
||||
|
||||
// Import text and dimensions (dxftext)
|
||||
bool m_importAnnotations = true;
|
||||
// Import Point tntities
|
||||
bool m_importPoints = true;
|
||||
// Import entities in Paper space
|
||||
bool m_importPaperSpaceEntities = false;
|
||||
// Import hidden blocks (the will not affect the result for standard import because such blocks
|
||||
// are never INSERTed but a custom derived class might want to access these block definitions.
|
||||
bool m_importHiddenBlocks = false;
|
||||
// Import content on Frozen layers
|
||||
bool m_importFrozenLayers = false;
|
||||
// Import content on Hidden layers. Note that an INSERT on a hidden layer would still be
|
||||
// expanded, but the resulting entities would not appear if placed on a hidden layer.
|
||||
bool m_importHiddenLayers = true;
|
||||
// NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
|
||||
// TODO: options still to implement:
|
||||
// Import Hatch boundaries as wires (FC doesn't support hatching (???) but this option will draw
|
||||
// a polygon of the boundary of
|
||||
// the hatched area
|
||||
// Render Polylines with width. If the width were constant we could use the View line width
|
||||
// property except that is in
|
||||
// pixels and does not scale. The legacy importer replaces the polyline with a face whose
|
||||
// side edges are the original polyline offset by half the width. If the polyline is open
|
||||
// these edges are joined by endcaps, otherwise they each join to their ends to form a face
|
||||
// with a hole in it.
|
||||
|
||||
// TODO: Other options we might want:
|
||||
// Import SOLIDs (as faces)
|
||||
|
||||
struct VertexInfo
|
||||
{
|
||||
Base::Vector3d location;
|
||||
double bulge = 0;
|
||||
};
|
||||
bool ExplodePolyline(std::list<VertexInfo>&, int flags);
|
||||
bool ReadBlockContents();
|
||||
bool SkipBlockContents();
|
||||
|
||||
private:
|
||||
// Error-handling control
|
||||
bool m_ignore_errors = true;
|
||||
bool m_fail = false;
|
||||
|
||||
// Mapping from layer name -> layer color index
|
||||
std::map<std::string, ColorIndex_t> m_layer_ColorIndex_map;
|
||||
const ColorIndex_t ColorBylayer = 256;
|
||||
const ColorIndex_t ColorByBlock = 0;
|
||||
// These could be private to CommonEntityAttributes but in the long run I want to make all
|
||||
// the DXF definitions available to the CDxfWrite as well.
|
||||
// The Default values are used if a Layer definition by error is missing an attribute,
|
||||
// and also if we synthesize a layer that has no definition.
|
||||
static const ColorIndex_t ColorBylayer = 256;
|
||||
static const ColorIndex_t ColorByBlock = 0;
|
||||
static const ColorIndex_t DefaultColor = 0;
|
||||
static const std::string LineTypeByLayer;
|
||||
static const std::string LineTypeByBlock;
|
||||
static const std::string DefaultLineType;
|
||||
|
||||
// Readers for various parts of the DXF file.
|
||||
bool ReadSection();
|
||||
@@ -487,8 +570,11 @@ private:
|
||||
bool ReadHeaderSection();
|
||||
bool ReadTablesSection();
|
||||
bool ReadBlocksSection();
|
||||
bool ReadEntitiesSection();
|
||||
|
||||
protected:
|
||||
virtual bool ReadEntitiesSection();
|
||||
|
||||
private:
|
||||
bool ReadIgnoredSection();
|
||||
|
||||
// The Header section consists of multipel variables, only a few of which we give special
|
||||
@@ -518,13 +604,7 @@ private:
|
||||
bool ReadSpline();
|
||||
bool ReadLwPolyLine();
|
||||
bool ReadPolyLine();
|
||||
struct VertexInfo
|
||||
{
|
||||
Base::Vector3d location;
|
||||
double bulge = 0;
|
||||
};
|
||||
|
||||
bool OnReadPolyline(std::list<VertexInfo>&, int flags);
|
||||
void OnReadArc(double start_angle,
|
||||
double end_angle,
|
||||
double radius,
|
||||
@@ -545,6 +625,7 @@ private:
|
||||
void InitializeAttributes();
|
||||
void InitializeCommonEntityAttributes();
|
||||
void Setup3DVectorAttribute(eDXFGroupCode_t x_record_type, Base::Vector3d& destination);
|
||||
void Setup3DDirectionAttribute(eDXFGroupCode_t x_record_type, Base::Vector3d& destination);
|
||||
void SetupScaledDoubleAttribute(eDXFGroupCode_t record_type, double& destination);
|
||||
void SetupScaledDoubleIntoList(eDXFGroupCode_t record_type, std::list<double>& destination);
|
||||
void Setup3DCoordinatesIntoLists(eDXFGroupCode_t x_record_type,
|
||||
@@ -556,6 +637,7 @@ private:
|
||||
static void ProcessScaledDouble(CDxfRead* object, void* target);
|
||||
static void ProcessScaledDoubleIntoList(CDxfRead* object, void* target);
|
||||
static void ProcessStdString(CDxfRead* object, void* target);
|
||||
static void ProcessLayerReference(CDxfRead* object, void* target);
|
||||
// For all types T where strean >> x and x = 0 works
|
||||
template<typename T>
|
||||
void SetupValueAttribute(eDXFGroupCode_t record_type, T& destination);
|
||||
@@ -574,6 +656,8 @@ private:
|
||||
|
||||
bool ProcessAttribute();
|
||||
void ProcessAllAttributes();
|
||||
void ProcessAllEntityAttributes();
|
||||
void ResolveEntityAttributes();
|
||||
|
||||
bool ReadBlockInfo();
|
||||
bool ResolveEncoding();
|
||||
@@ -584,13 +668,7 @@ private:
|
||||
bool (CDxfRead::*stringToUTF8)(std::string&) const = &CDxfRead::UTF8ToUTF8;
|
||||
|
||||
protected:
|
||||
// common entity properties. Some properties are accumulated local to the reader method and
|
||||
// passed to the ReadXxxx virtual method. Others are collected here as private values and also
|
||||
// passed to ReadXxxx. Finally some of the attributes are accessed using references to
|
||||
// public/protected fields or methods (such as LayerName()). Altogether a bit of a mishmash.
|
||||
// NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
ColorIndex_t m_ColorIndex = 0;
|
||||
std::string m_LineType;
|
||||
eDXFVersion_t m_version = RUnknown; // Version from $ACADVER variable in DXF
|
||||
// NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
|
||||
@@ -639,13 +717,128 @@ private:
|
||||
return m_record_data == testName;
|
||||
}
|
||||
|
||||
// Layer management
|
||||
protected:
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
Layer(const std::string& name, ColorIndex_t color, std::string&& lineType)
|
||||
: Name(name)
|
||||
, Color(color < 0 ? -color : color)
|
||||
, LineType(lineType)
|
||||
, Hidden(color < 0)
|
||||
{}
|
||||
Layer(const Layer&) = delete;
|
||||
Layer(Layer&&) = delete;
|
||||
void operator=(const Layer&) = delete;
|
||||
void operator=(Layer&&) = delete;
|
||||
virtual ~Layer() = default;
|
||||
const std::string Name;
|
||||
const ColorIndex_t Color;
|
||||
const std::string LineType;
|
||||
const bool Hidden;
|
||||
};
|
||||
std::map<std::string, Layer*> Layers;
|
||||
|
||||
virtual Layer* MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType);
|
||||
|
||||
// Entity attribute management
|
||||
class CommonEntityAttributes
|
||||
{
|
||||
public:
|
||||
Layer* m_Layer {nullptr};
|
||||
ColorIndex_t m_Color {0};
|
||||
std::string m_LineType;
|
||||
bool m_paperSpace {false};
|
||||
void SetDefaults()
|
||||
{
|
||||
// There is some uncertainty in the documentation. If the line type is omitted it should
|
||||
// act as by-layer.
|
||||
// The question is whether an explicit line type "BYLAYER" should also mean by-layer. If
|
||||
// this is the case we should set m_LineType to "BYLAYER" and only check for "BYLAYER"
|
||||
// in ResolveByLayerAttributes. If this is not the case and "BYLAYER" can actually refer
|
||||
// to a line type of that name, then we should init m_LineType to empty and check only
|
||||
// for a zero-length line type to trigger by-layer line type. An experiment in R13
|
||||
// reveals that it is not possible to make a line type BYLAYER so the former is what we
|
||||
// do.
|
||||
m_Color = ColorBylayer;
|
||||
m_LineType = LineTypeByLayer;
|
||||
m_paperSpace = false;
|
||||
}
|
||||
void ResolveBylayerAttributes(const CDxfRead& reader)
|
||||
{
|
||||
// This should be called after the entire entity is read in case the layer reference
|
||||
// name comes after a BYLAYER color. Also we can't be sure of BYLAYER line type until
|
||||
// the entity ends
|
||||
if (m_Color == ColorBylayer) {
|
||||
m_Color = m_Layer != nullptr ? m_Layer->Color : DefaultColor;
|
||||
}
|
||||
if (m_LineType == LineTypeByLayer) {
|
||||
m_LineType = m_Layer != nullptr ? m_Layer->LineType : DefaultLineType;
|
||||
}
|
||||
// We this point we not longer have any use for m_Layer except as a destination for the
|
||||
// entity. If the import is not preserving layers, we clear out that field here so all
|
||||
// entities get placed at the drawing root and are subject to compounding despite being
|
||||
// originally in different layers.
|
||||
if (!reader.m_preserveColors) {
|
||||
m_Color = DefaultColor;
|
||||
}
|
||||
if (!reader.m_preserveLayers) {
|
||||
m_Layer = nullptr;
|
||||
}
|
||||
}
|
||||
void ResolveByBlockAttributes(const CommonEntityAttributes& insertAttributes)
|
||||
{
|
||||
if (m_Color == ColorByBlock) {
|
||||
m_Color = insertAttributes.m_Color;
|
||||
}
|
||||
if (m_LineType == LineTypeByBlock) {
|
||||
m_LineType = insertAttributes.m_LineType;
|
||||
}
|
||||
}
|
||||
bool operator<(const CommonEntityAttributes& second) const
|
||||
{
|
||||
// TODO: This is here so we can use CommonEntityAttributes as a key in a std::map
|
||||
// It's been a while and I don't know if it is (currently) considered suspect to compare
|
||||
// for inequality two pointers that don't point within the same array, but I'm also not
|
||||
// sure what to do instead.
|
||||
return m_Layer < second.m_Layer
|
||||
|| (m_Layer == second.m_Layer
|
||||
&& (m_Color < second.m_Color
|
||||
|| (m_Color == second.m_Color
|
||||
&& (m_LineType < second.m_LineType
|
||||
|| (m_LineType == second.m_LineType && !m_paperSpace
|
||||
&& second.m_paperSpace)))));
|
||||
}
|
||||
};
|
||||
CommonEntityAttributes m_entityAttributes;
|
||||
// Coordinate transform management.
|
||||
private:
|
||||
// The normal vector as read from the eExtrusionDirection attributes, defaults to (0, 0, 1)
|
||||
// Reading this is set up in ReadEntity and done by ProcessAll(Entity)Attributes
|
||||
Base::Vector3d EntityNormalVector;
|
||||
|
||||
static constexpr double ArbitraryAxisAlgorithmThreshold = 1 / 64.0;
|
||||
|
||||
protected:
|
||||
// NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
// The transform implied by the Normal vector of the entity in EntityNormalVector.
|
||||
// This is a pure 3d rotation transformation (that is, no scaling or translation)
|
||||
// which maps the Z axis to the normal vector from the DXF records,
|
||||
// and the X and Y axes according to the Arbitrary Axis Algorithm in the DXF documentation
|
||||
// This is calculated from EntityNormalVector in ResolveEntityAttributes which is
|
||||
// called by either ProcessAllEntityAttributes or custom-processed entities after they
|
||||
// are finished calling ProcessAttribute.
|
||||
Base::Matrix4D OCSOrientationTransform;
|
||||
// NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||
|
||||
public:
|
||||
explicit CDxfRead(const std::string& filepath); // this opens the file
|
||||
CDxfRead(const CDxfRead&) = delete;
|
||||
CDxfRead(const CDxfRead&&) = delete;
|
||||
CDxfRead& operator=(const CDxfRead&) = delete;
|
||||
CDxfRead& operator=(const CDxfRead&&) = delete;
|
||||
virtual ~CDxfRead(); // this closes the file
|
||||
virtual ~CDxfRead();
|
||||
|
||||
bool Failed() const
|
||||
{
|
||||
@@ -653,6 +846,10 @@ public:
|
||||
}
|
||||
void
|
||||
DoRead(bool ignore_errors = false); // this reads the file and calls the following functions
|
||||
virtual void StartImport()
|
||||
{}
|
||||
virtual void FinishImport()
|
||||
{}
|
||||
|
||||
private:
|
||||
double mm(double value) const
|
||||
@@ -671,6 +868,10 @@ public:
|
||||
return (m_ignore_errors);
|
||||
}
|
||||
|
||||
virtual bool OnReadBlock(const std::string& /*name*/, int /*flags*/)
|
||||
{
|
||||
return SkipBlockContents();
|
||||
}
|
||||
virtual void
|
||||
OnReadLine(const Base::Vector3d& /*start*/, const Base::Vector3d& /*end*/, bool /*hidden*/)
|
||||
{}
|
||||
@@ -712,15 +913,14 @@ public:
|
||||
const Base::Vector3d& /*point*/,
|
||||
double /*rotation*/)
|
||||
{}
|
||||
virtual void AddGraphics() const
|
||||
virtual void OnReadPolyline(std::list<VertexInfo>& /*vertices*/, int /*flags*/)
|
||||
{}
|
||||
|
||||
// These give the derived class access to common object properties
|
||||
std::string LayerName() const;
|
||||
bool LineTypeIsHidden() const
|
||||
{
|
||||
return m_LineType[0] == 'h' || m_LineType[0] == 'H';
|
||||
return m_entityAttributes.m_LineType[0] == 'h' || m_entityAttributes.m_LineType[0] == 'H';
|
||||
}
|
||||
App::Color ObjectColor() const; // as rgba value
|
||||
static App::Color ObjectColor(ColorIndex_t colorIndex); // as rgba value
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -49,17 +49,8 @@
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#endif
|
||||
#include <regex>
|
||||
|
||||
// #include <App/Annotation.h>
|
||||
// #include <App/Application.h>
|
||||
// #include <App/Document.h>
|
||||
// #include <Base/Console.h>
|
||||
// #include <Base/Interpreter.h>
|
||||
// #include <Base/Matrix.h>
|
||||
// #include <Base/Parameter.h>
|
||||
// #include <Base/Vector3D.h>
|
||||
// #include <Base/PlacementPy.h>
|
||||
// #include <Mod/Part/App/PartFeature.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/ViewProvider.h>
|
||||
#include <Gui/ViewProviderDocumentObject.h>
|
||||
@@ -69,28 +60,28 @@
|
||||
|
||||
using namespace ImportGui;
|
||||
|
||||
ImpExpDxfReadGui::ImpExpDxfReadGui(std::string filepath, App::Document* pcDoc)
|
||||
ImpExpDxfReadGui::ImpExpDxfReadGui(const std::string& filepath, App::Document* pcDoc)
|
||||
: ImpExpDxfRead(filepath, pcDoc)
|
||||
, GuiDocument(Gui::Application::Instance->getDocument(pcDoc))
|
||||
{}
|
||||
|
||||
void ImpExpDxfReadGui::ApplyGuiStyles(Part::Feature* object)
|
||||
void ImpExpDxfReadGui::ApplyGuiStyles(Part::Feature* object) const
|
||||
{
|
||||
PartGui::ViewProviderPart* view =
|
||||
static_cast<PartGui::ViewProviderPart*>(GuiDocument->getViewProvider(object));
|
||||
App::Color color = ObjectColor();
|
||||
auto view = static_cast<PartGui::ViewProviderPart*>(GuiDocument->getViewProvider(object));
|
||||
App::Color color = ObjectColor(m_entityAttributes.m_Color);
|
||||
view->LineColor.setValue(color);
|
||||
view->PointColor.setValue(color);
|
||||
view->ShapeColor.setValue(color);
|
||||
view->DrawStyle.setValue(GetDrawStyle());
|
||||
view->Transparency.setValue(0);
|
||||
}
|
||||
|
||||
void ImpExpDxfReadGui::ApplyGuiStyles(App::FeaturePython* object)
|
||||
void ImpExpDxfReadGui::ApplyGuiStyles(App::FeaturePython* object) const
|
||||
{
|
||||
static Base::Type PropertyColorType = App::PropertyColor::getClassTypeId();
|
||||
|
||||
auto view = static_cast<Gui::ViewProviderDocumentObject*>(GuiDocument->getViewProvider(object));
|
||||
App::Color color = ObjectColor();
|
||||
App::Color color = ObjectColor(m_entityAttributes.m_Color);
|
||||
|
||||
// The properties on this object depend on which Python object is wrapped around it.
|
||||
// For now we look for the two colors we expect in text and dimensions, and check that they
|
||||
@@ -105,4 +96,50 @@ void ImpExpDxfReadGui::ApplyGuiStyles(App::FeaturePython* object)
|
||||
if (prop != nullptr && prop->getTypeId() == PropertyColorType) {
|
||||
static_cast<App::PropertyColor*>(prop)->setValue(color);
|
||||
}
|
||||
prop = view->getPropertyByName("DrawStyle");
|
||||
if (prop != nullptr && prop->getTypeId() == PropertyColorType) {
|
||||
static_cast<App::PropertyColor*>(prop)->setValue(GetDrawStyle());
|
||||
}
|
||||
}
|
||||
|
||||
int ImpExpDxfReadGui::GetDrawStyle() const
|
||||
{
|
||||
// The DXF line type can be quite complex, including both patterns, in-line text (for instance,
|
||||
// so a line can mark itself as "Gas Main"), and shapes from a .SHX file (so a line can have,
|
||||
// say, diamond shapes along it). The coin package we use to render objects into the screen has
|
||||
// no facility for doing the in-line text or shapes, and has limited control over line/space
|
||||
// patterns. The line pattern is divided into 16 pieces which can be either drawn or not. There
|
||||
// is also support for line pattern scaling (to match the LTSCALE DXF property). Unfortunately
|
||||
// neither of these is exposed at the FreeCAD level; line types are dumbed down to four choices:
|
||||
// 0 "Solid" 0xffff i.e. solid
|
||||
// 1 "Dashed" 0xf00f i.e. coarse dashes
|
||||
// 2 "Dotted" 0x0f0f i.e. fine (half-size) dashes
|
||||
// 3 "DashDot" 0xff88 i.e. long dash/dot (like a center line)
|
||||
// Right now we infer these from the DXF LINETYPE name. In the long run we should look at the
|
||||
// actual pattern, but the CDxfRead class does nothing with the LTYPE table. In the longer run,
|
||||
// FreeCAD should expose the line pattern (the 16-bit number) for more versatility.
|
||||
|
||||
// Make an array of patterns whose index is the "draw style" enum value for the pattern.
|
||||
static std::array<std::regex, 4> matchers {
|
||||
// Starts with "cont" (i.e. continuous) : return solid
|
||||
// I can't find anything that actually says what happens if you have a top-level (i.e. not
|
||||
// in a BLOCK) object whose line type is BYBLOCK. We treat this as Continuous.
|
||||
std::basic_regex("^cont|^byblock$", std::regex::icase),
|
||||
// starts with hidden, border, dash : return dashed
|
||||
std::basic_regex("^hidden|^border|^dash", std::regex::icase),
|
||||
// starts with dot : return dotted
|
||||
std::basic_regex("^dot", std::regex::icase),
|
||||
// dash & dot or center/centre/divide/phantom : return DashDot
|
||||
std::basic_regex("dot.*dash|dash.*dot|^cent(er|re)|^divide|^phantom", std::regex::icase)};
|
||||
// Some line type names can match several patterns, in particular dotdash can also match ^dot
|
||||
// and dashdot can match ^dash Rather than code the latter patterns to forbid subsequent "dash"
|
||||
// or "dot" so they are mutually exclusive, we just match the more specific pattern, which is
|
||||
// the last one, first.
|
||||
for (int i = matchers.size(); --i >= 0;) {
|
||||
if (regex_search(m_entityAttributes.m_LineType, matchers.at(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// If we don't understand it, return solid
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -37,14 +37,15 @@ namespace ImportGui
|
||||
class ImpExpDxfReadGui: public Import::ImpExpDxfRead
|
||||
{
|
||||
public:
|
||||
ImpExpDxfReadGui(std::string filepath, App::Document* pcDoc);
|
||||
ImpExpDxfReadGui(const std::string& filepath, App::Document* pcDoc);
|
||||
|
||||
protected:
|
||||
void ApplyGuiStyles(Part::Feature* object) override;
|
||||
void ApplyGuiStyles(App::FeaturePython* object) override;
|
||||
void ApplyGuiStyles(Part::Feature* object) const override;
|
||||
void ApplyGuiStyles(App::FeaturePython* object) const override;
|
||||
|
||||
private:
|
||||
Gui::Document* GuiDocument;
|
||||
int GetDrawStyle() const;
|
||||
};
|
||||
} // namespace ImportGui
|
||||
|
||||
|
||||
Reference in New Issue
Block a user