Import: DXF, first working version for import as Part primitives
This commit is contained in:
@@ -76,8 +76,8 @@ class DxfImportDialog:
|
||||
self.dialog.radio_ImportAs_Shapes.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Fused.setEnabled(True)
|
||||
else:
|
||||
self.dialog.radio_ImportAs_Draft.setEnabled(False)
|
||||
self.dialog.radio_ImportAs_Primitives.setEnabled(False)
|
||||
self.dialog.radio_ImportAs_Draft.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Primitives.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Shapes.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Fused.setEnabled(True)
|
||||
|
||||
|
||||
@@ -111,19 +111,19 @@ the 'dxf_library' addon from the Addon Manager.</string>
|
||||
<item>
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Draft">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Creates fully parametric Draft objects. Block definitions are imported as
|
||||
reusable objects (Part Compounds) and instances become `App::Link` objects,
|
||||
maintaining the block structure. Best for full integration with the Draft
|
||||
Workbench. (Legacy importer only)</string>
|
||||
Workbench. </string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Editable draft objects (Highest fidelity, slowest)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>DxfImportMode</cstring>
|
||||
<cstring>dxfImportAsDraft</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
@@ -139,19 +139,19 @@ Workbench. (Legacy importer only)</string>
|
||||
<item>
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Primitives">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Creates parametric Part objects (e.g., Part::Line, Part::Circle). Block
|
||||
definitions are imported as reusable objects (Part Compounds) and instances
|
||||
become `App::Link` objects, maintaining the block structure. Best for
|
||||
script-based post-processing. (Not yet implemented)</string>
|
||||
script-based post-processing and Part Workbench integration.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Editable part primitives (High fidelity, slower)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>DxfImportMode</cstring>
|
||||
<cstring>dxfImportAsPrimitives</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
@@ -178,7 +178,7 @@ objects, maintaining the block structure. Good for referencing and measuring.</s
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>DxfImportMode</cstring>
|
||||
<cstring>dxfImportAsShapes</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
@@ -196,13 +196,13 @@ objects, maintaining the block structure. Good for referencing and measuring.</s
|
||||
<property name="toolTip">
|
||||
<string>Merges all geometry per layer into a single, non-editable shape. Block
|
||||
structures are not preserved; their geometry becomes part of the layer's
|
||||
shape. Best for viewing very large files with maximum performance.</string>
|
||||
shape. Best for importing and viewing very large files with maximum performance.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fused part shapes (Lowest fidelity, fastest)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>DxfImportMode</cstring>
|
||||
<cstring>dxfImportAsFused</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
|
||||
@@ -2790,12 +2790,117 @@ def warn(dxfobject, num=None):
|
||||
badobjects.append(dxfobject)
|
||||
|
||||
|
||||
def _import_dxf_file(filename, doc_name=None):
|
||||
"""
|
||||
Internal helper to handle the core logic for both open and insert.
|
||||
"""
|
||||
hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
||||
use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
readPreferences()
|
||||
|
||||
# --- Dialog Workflow ---
|
||||
try:
|
||||
if gui:
|
||||
FreeCADGui.suspendWaitCursor()
|
||||
|
||||
if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True):
|
||||
try:
|
||||
import ImportGui
|
||||
entity_counts = ImportGui.preScanDxf(filename)
|
||||
except Exception:
|
||||
entity_counts = {}
|
||||
|
||||
from DxfImportDialog import DxfImportDialog
|
||||
dlg = DxfImportDialog(entity_counts)
|
||||
|
||||
if dlg.exec_():
|
||||
# Save the integer mode from the pop-up dialog.
|
||||
hGrp.SetInt("DxfImportMode", dlg.get_selected_mode())
|
||||
|
||||
# Keep the main preferences booleans
|
||||
# in sync with the choice just made in the pop-up dialog.
|
||||
mode = dlg.get_selected_mode()
|
||||
params.set_param("dxfImportAsDraft", mode == 0)
|
||||
params.set_param("dxfImportAsPrimitives", mode == 1)
|
||||
params.set_param("dxfImportAsShapes", mode == 2)
|
||||
params.set_param("dxfImportAsFused", mode == 3)
|
||||
hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again())
|
||||
else:
|
||||
return None, None, None, None # Return None to indicate cancellation
|
||||
finally:
|
||||
if gui:
|
||||
FreeCADGui.resumeWaitCursor()
|
||||
|
||||
import_mode = hGrp.GetInt("DxfImportMode", 2)
|
||||
|
||||
# --- Document Handling ---
|
||||
if doc_name: # INSERT operation
|
||||
try:
|
||||
doc = FreeCAD.getDocument(doc_name)
|
||||
except NameError:
|
||||
doc = FreeCAD.newDocument(doc_name)
|
||||
FreeCAD.setActiveDocument(doc_name)
|
||||
else: # OPEN operation
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
doc.Label = docname
|
||||
FreeCAD.setActiveDocument(doc.Name)
|
||||
|
||||
# --- Core Import Execution ---
|
||||
processing_start_time = time.perf_counter()
|
||||
|
||||
if is_draft_mode:
|
||||
# For Draft mode, we tell the C++ importer to create Part Primitives first.
|
||||
hGrp.SetInt("DxfImportMode", 1)
|
||||
|
||||
# Take snapshot of objects before import
|
||||
objects_before = set(doc.Objects)
|
||||
|
||||
stats = None # For C++ importer stats
|
||||
if use_legacy:
|
||||
getDXFlibs()
|
||||
if dxfReader:
|
||||
processdxf(doc, filename)
|
||||
else:
|
||||
errorDXFLib(gui)
|
||||
return None, None
|
||||
else: # Modern C++ Importer
|
||||
if gui:
|
||||
import ImportGui
|
||||
stats = ImportGui.readDXF(filename)
|
||||
else:
|
||||
import Import
|
||||
stats = Import.readDXF(filename)
|
||||
|
||||
# Find the newly created objects
|
||||
objects_after = set(doc.Objects)
|
||||
newly_created_objects = objects_after - objects_before
|
||||
|
||||
# Restore the original mode setting if we changed it
|
||||
if is_draft_mode:
|
||||
hGrp.SetInt("DxfImportMode", 0)
|
||||
|
||||
# --- Post-processing step ---
|
||||
if is_draft_mode and newly_created_objects:
|
||||
post_process_to_draft(doc, newly_created_objects)
|
||||
|
||||
Draft.convert_draft_texts() # This is a general utility that should run for both importers
|
||||
doc.recompute()
|
||||
|
||||
processing_end_time = time.perf_counter()
|
||||
|
||||
# Return the results for the reporter
|
||||
return doc, stats, processing_start_time, processing_end_time
|
||||
|
||||
# --- REFACTORED open() and insert() functions ---
|
||||
|
||||
def open(filename):
|
||||
"""Open a file and return a new document.
|
||||
|
||||
If the global variable `dxfUseLegacyImporter` exists,
|
||||
it will process `filename` with `processdxf`.
|
||||
Otherwise, it will use the `Import` module, `Import.readDXF(filename)`.
|
||||
This function handles the import of a DXF file into a new document.
|
||||
It shows an import dialog for the modern C++ importer if configured to do so.
|
||||
It manages the import workflow, including pre-processing, calling the
|
||||
correct backend (legacy or modern C++), and post-processing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -2804,175 +2909,38 @@ def open(filename):
|
||||
|
||||
Returns
|
||||
-------
|
||||
App::Document
|
||||
The new document object with objects and shapes built from `filename`.
|
||||
|
||||
To do
|
||||
-----
|
||||
Use local variables, not global variables.
|
||||
App::Document or None
|
||||
The new document object with imported content, or None if the
|
||||
operation was cancelled or failed.
|
||||
"""
|
||||
hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
||||
use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=None)
|
||||
|
||||
# The C++ layer (`Gui::Application::importFrom`) has set the WaitCursor.
|
||||
# We must temporarily suspend it to show our interactive dialog.
|
||||
try:
|
||||
if gui:
|
||||
FreeCADGui.suspendWaitCursor()
|
||||
|
||||
# --- Dialog Workflow ---
|
||||
if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True):
|
||||
try:
|
||||
import ImportGui
|
||||
# This C++ function will need to be created in a later step
|
||||
entity_counts = ImportGui.preScanDxf(filename)
|
||||
except Exception:
|
||||
entity_counts = {}
|
||||
|
||||
from DxfImportDialog import DxfImportDialog
|
||||
dlg = DxfImportDialog(entity_counts)
|
||||
|
||||
if dlg.exec_():
|
||||
# User clicked OK, save settings from the dialog
|
||||
hGrp.SetInt("DxfImportMode", dlg.get_selected_mode())
|
||||
hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again())
|
||||
else:
|
||||
# User clicked Cancel, abort the entire operation
|
||||
FCC.PrintLog("DXF import cancelled by user.\n")
|
||||
return
|
||||
else:
|
||||
# If we don't show the dialog, we still need to read preferences
|
||||
# to ensure the correct backend logic is triggered.
|
||||
readPreferences()
|
||||
|
||||
finally:
|
||||
# --- CRITICAL: Always resume the wait state before returning to C++ ---
|
||||
# This restores the wait cursor and event filter so the subsequent
|
||||
# blocking C++ call behaves as expected within the C++ scope.
|
||||
if gui:
|
||||
FreeCADGui.resumeWaitCursor()
|
||||
|
||||
# --- Proceed with the blocking import logic ---
|
||||
total_start_time = time.perf_counter()
|
||||
|
||||
if use_legacy:
|
||||
getDXFlibs()
|
||||
if dxfReader:
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
doc.Label = docname
|
||||
processdxf(doc, filename)
|
||||
return doc
|
||||
else:
|
||||
errorDXFLib(gui)
|
||||
return None
|
||||
else: # Modern C++ Importer
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
doc.Label = docname
|
||||
FreeCAD.setActiveDocument(doc.Name)
|
||||
stats = None
|
||||
|
||||
if gui:
|
||||
import ImportGui
|
||||
stats = ImportGui.readDXF(filename)
|
||||
else:
|
||||
import Import
|
||||
stats = Import.readDXF(filename)
|
||||
|
||||
Draft.convert_draft_texts()
|
||||
doc.recompute()
|
||||
|
||||
total_end_time = time.perf_counter()
|
||||
if stats:
|
||||
# Report PROCESSING time only, not user dialog time.
|
||||
reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
return doc
|
||||
if doc and stats:
|
||||
reporter = DxfImportReporter(filename, stats, end_time - start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
return doc
|
||||
|
||||
def insert(filename, docname):
|
||||
"""Import a file into the specified document.
|
||||
|
||||
This function handles the import of a DXF file into a specified document.
|
||||
If the document does not exist, it will be created. It shows an import
|
||||
dialog for the modern C++ importer if configured to do so.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
The path to the file to import.
|
||||
|
||||
docname : str
|
||||
The name of an `App::Document` instance into which
|
||||
the objects and shapes from `filename` will be imported.
|
||||
|
||||
If the document doesn't exist, it is created
|
||||
and set as the active document.
|
||||
|
||||
To do
|
||||
-----
|
||||
Use local variables, not global variables.
|
||||
The name of an App::Document instance to import the content into.
|
||||
"""
|
||||
hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
||||
use_legacy = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=docname)
|
||||
|
||||
try:
|
||||
if gui:
|
||||
FreeCADGui.suspendWaitCursor()
|
||||
if doc and stats:
|
||||
reporter = DxfImportReporter(filename, stats, end_time - start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
# --- Dialog Workflow ---
|
||||
if gui and not use_legacy and hGrp.GetBool("dxfShowDialog", True):
|
||||
try:
|
||||
import ImportGui
|
||||
entity_counts = ImportGui.preScanDxf(filename)
|
||||
except Exception:
|
||||
entity_counts = {}
|
||||
|
||||
from DxfImportDialog import DxfImportDialog
|
||||
dlg = DxfImportDialog(entity_counts)
|
||||
|
||||
if dlg.exec_():
|
||||
hGrp.SetInt("DxfImportMode", dlg.get_selected_mode())
|
||||
hGrp.SetBool("dxfShowDialog", dlg.get_show_dialog_again())
|
||||
else:
|
||||
FCC.PrintLog("DXF insert cancelled by user.\n")
|
||||
return
|
||||
else:
|
||||
readPreferences()
|
||||
|
||||
finally:
|
||||
if gui:
|
||||
FreeCADGui.resumeWaitCursor()
|
||||
|
||||
# --- Proceed with the blocking insert logic ---
|
||||
total_start_time = time.perf_counter()
|
||||
|
||||
try:
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
except NameError:
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
FreeCAD.setActiveDocument(docname)
|
||||
|
||||
if use_legacy:
|
||||
getDXFlibs()
|
||||
if dxfReader:
|
||||
processdxf(doc, filename)
|
||||
else:
|
||||
errorDXFLib(gui)
|
||||
else: # Modern C++ Importer
|
||||
stats = None
|
||||
if gui:
|
||||
import ImportGui
|
||||
stats = ImportGui.readDXF(filename)
|
||||
else:
|
||||
import Import
|
||||
stats = Import.readDXF(filename)
|
||||
|
||||
Draft.convert_draft_texts()
|
||||
doc.recompute()
|
||||
|
||||
total_end_time = time.perf_counter()
|
||||
if stats:
|
||||
reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
def getShapes(filename):
|
||||
"""Read a DXF file, and return a list of shapes from its contents.
|
||||
@@ -4274,32 +4242,46 @@ def readPreferences():
|
||||
|
||||
dxfUseLegacyImporter = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
|
||||
# This logic is now only needed for the legacy importer.
|
||||
# Synchronization Bridge (Booleans -> Integer)
|
||||
# Read the boolean parameters from the main preferences dialog. Based on which one is true, set
|
||||
# the single 'DxfImportMode' integer parameter that the C++ importer and legacy importer logic
|
||||
# rely on. This ensures the setting from the main preferences is always respected at the start
|
||||
# of an import.
|
||||
if hGrp.GetBool("dxfImportAsDraft", False):
|
||||
import_mode = 0
|
||||
elif hGrp.GetBool("dxfImportAsPrimitives", False):
|
||||
import_mode = 1
|
||||
elif hGrp.GetBool("dxfImportAsFused", False):
|
||||
import_mode = 3
|
||||
else: # Default to "Individual part shapes"
|
||||
import_mode = 2
|
||||
hGrp.SetInt("DxfImportMode", import_mode)
|
||||
|
||||
# The legacy importer logic now reads the unified import_mode integer.
|
||||
# The modern importer reads its settings directly in C++.
|
||||
if dxfUseLegacyImporter:
|
||||
# Legacy override for sketch creation takes highest priority
|
||||
dxfCreateSketch = hGrp.GetBool("dxfCreateSketch", False)
|
||||
|
||||
if dxfCreateSketch:
|
||||
if dxfCreateSketch: # dxfCreateSketch overrides the import mode for the legacy importer
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = False
|
||||
dxfMakeBlocks = False
|
||||
else:
|
||||
# Read the new unified mode parameter and translate it to the old flags
|
||||
# 0=Draft, 1=Primitives, 2=Shapes, 3=Fused
|
||||
import_mode = hGrp.GetInt("DxfImportMode", 2) # Default to "Individual shapes"
|
||||
if import_mode == 3: # Fused part shapes
|
||||
dxfMakeBlocks = True
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = False
|
||||
elif import_mode == 0: # Editable draft objects
|
||||
dxfMakeBlocks = False
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = True
|
||||
else: # Individual part shapes or Primitives
|
||||
dxfMakeBlocks = False
|
||||
dxfCreatePart = True
|
||||
dxfCreateDraft = False
|
||||
# The 'import_mode' variable is now set by the UI synchronization bridge that runs just
|
||||
# before this block. We now translate the existing 'import_mode' variable into the old
|
||||
# flags.
|
||||
elif import_mode == 0: # Editable draft objects
|
||||
dxfMakeBlocks = False
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = True
|
||||
elif import_mode == 3: # Fused part shapes
|
||||
dxfMakeBlocks = True
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = False
|
||||
else: # Individual part shapes or Primitives (modes 1 and 2)
|
||||
dxfMakeBlocks = False
|
||||
dxfCreatePart = True
|
||||
dxfCreateDraft = False
|
||||
|
||||
# The legacy importer still uses these global variables, so we read them all.
|
||||
dxfDiscretizeCurves = hGrp.GetBool("DiscretizeEllipses", True)
|
||||
@@ -4321,6 +4303,79 @@ def readPreferences():
|
||||
dxfBrightBackground = isBrightBackground()
|
||||
dxfDefaultColor = getColor()
|
||||
|
||||
|
||||
def post_process_to_draft(doc, new_objects):
|
||||
"""
|
||||
Converts a list of newly created Part primitives and placeholders
|
||||
into their corresponding Draft objects.
|
||||
"""
|
||||
if not new_objects:
|
||||
return
|
||||
|
||||
FCC.PrintMessage("Post-processing {} objects to Draft types...\n".format(len(new_objects)))
|
||||
|
||||
objects_to_delete = []
|
||||
|
||||
for obj in list(new_objects): # Iterate over a copy
|
||||
if App.isdeleted(obj):
|
||||
continue
|
||||
|
||||
if obj.isDerivedFrom("Part::Feature"):
|
||||
# Handles Part::Vertex, Part::Line, Part::Circle, Part::Compound,
|
||||
# and Part::Features containing Ellipses/Splines.
|
||||
try:
|
||||
Draft.upgrade([obj], delete=True)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning("Could not upgrade {} to Draft object: {}\n".format(obj.Label, str(e)))
|
||||
|
||||
elif obj.isDerivedFrom("App::FeaturePython") and hasattr(obj, "DxfEntityType"):
|
||||
# This is one of our placeholders
|
||||
entity_type = obj.DxfEntityType
|
||||
|
||||
if entity_type == "DIMENSION":
|
||||
try:
|
||||
# 1. Create an empty Draft Dimension
|
||||
dim = doc.addObject("App::FeaturePython", "Dimension")
|
||||
Draft.Dimension(dim)
|
||||
if gui:
|
||||
from Draft import _ViewProviderDimension
|
||||
_ViewProviderDimension(dim.ViewObject)
|
||||
|
||||
# 2. Copy properties directly from the placeholder
|
||||
dim.Start = obj.Start
|
||||
dim.End = obj.End
|
||||
dim.Dimline = obj.Dimline
|
||||
dim.Placement = obj.Placement
|
||||
|
||||
objects_to_delete.append(obj)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning("Could not create Draft Dimension from {}: {}\n".format(obj.Label, str(e)))
|
||||
|
||||
elif entity_type == "TEXT":
|
||||
try:
|
||||
# 1. Create a Draft Text object
|
||||
text_obj = Draft.make_text(obj.Text)
|
||||
|
||||
# 2. Copy properties
|
||||
text_obj.Placement = obj.Placement
|
||||
if gui:
|
||||
# TEXTSCALING is a global defined at the top of importDXF.py
|
||||
text_obj.ViewObject.FontSize = obj.DxfTextHeight * TEXTSCALING
|
||||
|
||||
objects_to_delete.append(obj)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning("Could not create Draft Text from {}: {}\n".format(obj.Label, str(e)))
|
||||
|
||||
# Perform the deletion of placeholders after the loop
|
||||
for obj in objects_to_delete:
|
||||
try:
|
||||
doc.removeObject(obj.Name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
doc.recompute()
|
||||
|
||||
|
||||
class DxfImportReporter:
|
||||
"""Formats and reports statistics from a DXF import process."""
|
||||
def __init__(self, filename, stats_dict, total_time=0.0):
|
||||
|
||||
@@ -28,28 +28,37 @@
|
||||
#include <BRepAdaptor_HCurve.hxx>
|
||||
#endif
|
||||
#include <Approx_Curve3d.hxx>
|
||||
#include <BRepAdaptor_CompCurve.hxx>
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_Transform.hxx>
|
||||
#include <BRepBuilderAPI_GTransform.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <GCPnts_UniformAbscissa.hxx>
|
||||
#include <GeomAPI_Interpolate.hxx>
|
||||
#include <GeomAPI_PointsToBSpline.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <gp_Ax1.hxx>
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <gp_Circ.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
#include <gp_Elips.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#endif
|
||||
|
||||
@@ -66,10 +75,12 @@
|
||||
#include <Base/Parameter.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
#include <Mod/Part/App/FeatureCompound.h>
|
||||
#include <Mod/Part/App/PrimitiveFeature.h>
|
||||
#include <Mod/Part/App/FeaturePartCircle.h>
|
||||
#include <App/Link.h>
|
||||
#include <App/FeaturePython.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "ImpExpDxf.h"
|
||||
@@ -81,6 +92,81 @@ using namespace Import;
|
||||
using BRepAdaptor_HCurve = BRepAdaptor_Curve;
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::map<std::string, int> ImpExpDxfRead::PreScan(const std::string& filepath)
|
||||
{
|
||||
std::map<std::string, int> counts;
|
||||
@@ -136,17 +222,6 @@ void ImpExpDxfRead::StartImport()
|
||||
|
||||
bool ImpExpDxfRead::ReadEntitiesSection()
|
||||
{
|
||||
// TODO: remove this once the unsupported modes have been implemented.
|
||||
// Perform a one-time check for unsupported modes
|
||||
if (m_importMode == ImportMode::EditableDraft) {
|
||||
UnsupportedFeature("Import as 'Editable draft objects' is not yet implemented.");
|
||||
// We can continue, and the switch statements below will do nothing,
|
||||
// resulting in an empty import for geometry, which is correct behavior.
|
||||
}
|
||||
else if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
UnsupportedFeature("Import as 'Editable part primitives' is not yet implemented.");
|
||||
}
|
||||
|
||||
// After parsing the BLOCKS section, compose all block definitions
|
||||
// into FreeCAD objects before processing the ENTITIES section.
|
||||
ComposeBlocks();
|
||||
@@ -284,8 +359,10 @@ void ImpExpDxfRead::ComposeFlattenedBlock(const std::string& blockName,
|
||||
std::list<TopoDS_Shape> shapeCollection;
|
||||
|
||||
// 4. Process primitive geometry.
|
||||
for (const auto& [attributes, shapeList] : blockData.Shapes) {
|
||||
shapeCollection.insert(shapeCollection.end(), shapeList.begin(), shapeList.end());
|
||||
for (const auto& [attributes, builderList] : blockData.GeometryBuilders) {
|
||||
for (const auto& builder : builderList) {
|
||||
shapeCollection.push_back(builder.shape);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Process nested inserts recursively.
|
||||
@@ -351,8 +428,7 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName,
|
||||
const Block& blockData = it->second;
|
||||
|
||||
// 3. Create the master Part::Compound for this block definition.
|
||||
std::string compName = "BLOCK_";
|
||||
compName += blockName;
|
||||
std::string compName = "BLOCK_" + blockName;
|
||||
auto blockCompound = document->addObject<Part::Compound>(
|
||||
document->getUniqueObjectName(compName.c_str()).c_str());
|
||||
m_blockDefinitionGroup->addObject(blockCompound);
|
||||
@@ -360,7 +436,7 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName,
|
||||
blockCompound->Visibility.setValue(false);
|
||||
this->m_blockDefinitions[blockName] = blockCompound;
|
||||
|
||||
std::vector<App::DocumentObject*> linkedObjects;
|
||||
std::vector<App::DocumentObject*> childObjects;
|
||||
|
||||
// 4. Recursively Compose and Link Nested Inserts.
|
||||
for (const auto& insertAttrPair : blockData.Inserts) {
|
||||
@@ -387,96 +463,103 @@ void ImpExpDxfRead::ComposeParametricBlock(const std::string& blockName,
|
||||
link->ScaleVector.setValue(nestedInsert.Scale);
|
||||
link->Visibility.setValue(false);
|
||||
IncrementCreatedObjectCount();
|
||||
linkedObjects.push_back(link);
|
||||
childObjects.push_back(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Create and Link Primitive Geometry.
|
||||
// Iterate through each attribute group (e.g., each layer within the block).
|
||||
for (const auto& [attributes, shapeList] : blockData.Shapes) {
|
||||
// Then, iterate through each shape in that group and create a separate feature for it.
|
||||
for (const auto& shape : shapeList) {
|
||||
if (!shape.IsNull()) {
|
||||
std::string cleanBlockLabel = blockName;
|
||||
if (!cleanBlockLabel.empty() && std::isdigit(cleanBlockLabel[0])) {
|
||||
// Workaround for FreeCAD's unique name generator, which prepends an underscore
|
||||
// to names that start with a digit. We add our own prefix.
|
||||
cleanBlockLabel.insert(0, "_");
|
||||
// 5. Create and Link Primitive Geometry from the collected builders.
|
||||
for (const auto& [attributes, builderList] : blockData.GeometryBuilders) {
|
||||
this->m_entityAttributes = attributes;
|
||||
|
||||
for (const auto& builder : builderList) {
|
||||
App::DocumentObject* newObject = nullptr;
|
||||
switch (builder.type) {
|
||||
case GeometryBuilder::PrimitiveType::None: {
|
||||
auto* p = document->addObject<Part::Feature>("Shape");
|
||||
p->Shape.setValue(builder.shape);
|
||||
newObject = p;
|
||||
break;
|
||||
}
|
||||
else if (!cleanBlockLabel.empty() && std::isdigit(cleanBlockLabel.back())) {
|
||||
// Add a trailing underscore to prevent the unique name generator
|
||||
// from incrementing the number in the block's name.
|
||||
cleanBlockLabel += "_";
|
||||
case GeometryBuilder::PrimitiveType::Point: {
|
||||
auto* p = document->addObject<Part::Vertex>("Point");
|
||||
TopoDS_Vertex v = TopoDS::Vertex(builder.shape);
|
||||
gp_Pnt pnt = BRep_Tool::Pnt(v);
|
||||
p->Placement.setValue(Base::Placement(Base::Vector3d(pnt.X(), pnt.Y(), pnt.Z()),
|
||||
Base::Rotation()));
|
||||
newObject = p;
|
||||
break;
|
||||
}
|
||||
// Determine a more descriptive name for the primitive feature.
|
||||
std::string type_suffix = "Shape";
|
||||
if (shape.ShapeType() == TopAbs_EDGE) {
|
||||
BRepAdaptor_Curve adaptor(TopoDS::Edge(shape));
|
||||
switch (adaptor.GetType()) {
|
||||
case GeomAbs_Line:
|
||||
type_suffix = "Line";
|
||||
break;
|
||||
case GeomAbs_Circle:
|
||||
type_suffix = "Circle";
|
||||
break;
|
||||
case GeomAbs_Ellipse:
|
||||
type_suffix = "Ellipse";
|
||||
break;
|
||||
case GeomAbs_BSplineCurve:
|
||||
type_suffix = "BSpline";
|
||||
break;
|
||||
case GeomAbs_BezierCurve:
|
||||
type_suffix = "Bezier";
|
||||
break;
|
||||
default:
|
||||
type_suffix = "Edge";
|
||||
break;
|
||||
case GeometryBuilder::PrimitiveType::Line: {
|
||||
newObject = createLinePrimitive(TopoDS::Edge(builder.shape), document, "Line");
|
||||
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; // Helper function failed
|
||||
}
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_VERTEX) {
|
||||
type_suffix = "Vertex";
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_WIRE) {
|
||||
type_suffix = "Wire";
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_FACE) {
|
||||
type_suffix = "Face";
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_SHELL) {
|
||||
type_suffix = "Shell";
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_SOLID) {
|
||||
type_suffix = "Solid";
|
||||
}
|
||||
else if (shape.ShapeType() == TopAbs_COMPOUND) {
|
||||
type_suffix = "Compound";
|
||||
}
|
||||
|
||||
std::string primitive_base_label = cleanBlockLabel + "_" + type_suffix;
|
||||
// Use getStandardObjectLabel to get a unique user-facing label (e.g.,
|
||||
// "block01_Line001") while keeping the internal object name clean.
|
||||
auto geomFeature = document->addObject<Part::Feature>(
|
||||
document->getStandardObjectLabel(primitive_base_label.c_str(), 3).c_str());
|
||||
// For full circles, ensure the angles span the full 360 degrees
|
||||
if (builder.type == GeometryBuilder::PrimitiveType::Circle) {
|
||||
p->Angle1.setValue(0.0);
|
||||
p->Angle2.setValue(360.0);
|
||||
}
|
||||
newObject = p;
|
||||
break;
|
||||
}
|
||||
case GeometryBuilder::PrimitiveType::PolylineCompound: {
|
||||
auto* p = document->addObject<Part::Compound>("Polyline");
|
||||
std::vector<App::DocumentObject*> segments;
|
||||
TopExp_Explorer explorer(builder.shape, TopAbs_EDGE);
|
||||
for (; explorer.More(); explorer.Next()) {
|
||||
TopoDS_Edge edge = TopoDS::Edge(explorer.Current());
|
||||
BRepAdaptor_Curve adaptor(edge);
|
||||
App::DocumentObject* segment = nullptr;
|
||||
if (adaptor.GetType() == GeomAbs_Line) {
|
||||
segment = createLinePrimitive(edge, document, "Segment");
|
||||
}
|
||||
else if (adaptor.GetType() == GeomAbs_Circle) {
|
||||
auto* arc = createCirclePrimitive(edge, document, "Arc");
|
||||
segment = arc;
|
||||
}
|
||||
|
||||
if (segment) {
|
||||
IncrementCreatedObjectCount();
|
||||
segment->Visibility.setValue(false);
|
||||
ApplyGuiStyles(static_cast<Part::Feature*>(segment));
|
||||
segments.push_back(segment);
|
||||
}
|
||||
}
|
||||
p->Links.setValues(segments);
|
||||
newObject = p;
|
||||
break;
|
||||
}
|
||||
case GeometryBuilder::PrimitiveType::Spline:
|
||||
case GeometryBuilder::PrimitiveType::Ellipse:
|
||||
default:
|
||||
// Fallback for types without a specific primitive
|
||||
auto* p = document->addObject<Part::Feature>("Shape");
|
||||
p->Shape.setValue(builder.shape);
|
||||
newObject = p;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newObject) {
|
||||
IncrementCreatedObjectCount();
|
||||
geomFeature->Shape.setValue(shape);
|
||||
geomFeature->Visibility.setValue(false);
|
||||
|
||||
// Apply styling to this primitive feature using its original attributes.
|
||||
this->m_entityAttributes = attributes;
|
||||
this->ApplyGuiStyles(geomFeature);
|
||||
|
||||
linkedObjects.push_back(geomFeature);
|
||||
newObject->Visibility.setValue(false);
|
||||
ApplyGuiStyles(static_cast<Part::Feature*>(newObject));
|
||||
childObjects.push_back(newObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add similar logic for blockData.FeatureBuildersList if needed.
|
||||
|
||||
// 6. Finalize the Part::Compound.
|
||||
if (!linkedObjects.empty()) {
|
||||
blockCompound->Links.setValues(linkedObjects);
|
||||
if (!childObjects.empty()) {
|
||||
blockCompound->Links.setValues(childObjects);
|
||||
}
|
||||
|
||||
// 7. Mark this block as composed.
|
||||
@@ -487,7 +570,7 @@ void ImpExpDxfRead::ComposeBlocks()
|
||||
{
|
||||
std::set<std::string> composedBlocks;
|
||||
|
||||
if (m_mergeOption == MergeShapes) {
|
||||
if (m_importMode == ImportMode::FusedShapes) {
|
||||
// User wants flattened geometry for performance.
|
||||
for (const auto& pair : this->Blocks) {
|
||||
if (composedBlocks.find(pair.first) == composedBlocks.end()) {
|
||||
@@ -612,8 +695,7 @@ bool ImpExpDxfRead::OnReadBlock(const std::string& name, int flags)
|
||||
// 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.Shapes,
|
||||
temporaryBlock.FeatureBuildersList,
|
||||
temporaryBlock.GeometryBuilders,
|
||||
temporaryBlock.Inserts);
|
||||
if (!ReadBlockContents()) {
|
||||
return false; // Abort on parsing error
|
||||
@@ -632,24 +714,17 @@ void ImpExpDxfRead::OnReadLine(const Base::Vector3d& start,
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_importMode) {
|
||||
case ImportMode::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
gp_Pnt p0 = makePoint(start);
|
||||
gp_Pnt p1 = makePoint(end);
|
||||
// TODO: Really?? What about the people designing integrated circuits?
|
||||
if (p0.IsEqual(p1, 1e-8)) {
|
||||
return;
|
||||
}
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(p0, p1).Edge(), "Line");
|
||||
break;
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
// Do nothing until these modes have been implemented, the one-time warning has already
|
||||
// been issued.
|
||||
break;
|
||||
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);
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Line;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -658,20 +733,13 @@ 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::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
// For non-parametric modes, create a Part::Feature with a Vertex shape.
|
||||
Collector->AddObject(BRepBuilderAPI_MakeVertex(makePoint(start)).Vertex(), "Point");
|
||||
break;
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
// Do nothing until these modes have been implemented, the one-time warning has already
|
||||
// been issued.
|
||||
break;
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Point;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -685,31 +753,25 @@ void ImpExpDxfRead::OnReadArc(const Base::Vector3d& start,
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_importMode) {
|
||||
case ImportMode::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
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) {
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(circle, p0, p1).Edge(), "Arc");
|
||||
}
|
||||
else {
|
||||
Base::Console().warning("ImpExpDxf - ignore degenerate arc of circle\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
// Do nothing until these modes have been implemented, the one-time warning has already
|
||||
// been issued.
|
||||
break;
|
||||
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);
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Arc;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -722,28 +784,24 @@ void ImpExpDxfRead::OnReadCircle(const Base::Vector3d& start,
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_importMode) {
|
||||
case ImportMode::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
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) {
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(circle).Edge(), "Circle");
|
||||
}
|
||||
else {
|
||||
Base::Console().warning("ImpExpDxf - ignore degenerate circle\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
break;
|
||||
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);
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Circle;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -847,32 +905,25 @@ void ImpExpDxfRead::OnReadSpline(struct SplineData& sd)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_importMode) {
|
||||
case ImportMode::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
try {
|
||||
Handle(Geom_BSplineCurve) geom;
|
||||
if (sd.control_points > 0) {
|
||||
geom = getSplineFromPolesAndKnots(sd);
|
||||
}
|
||||
else if (sd.fit_points > 0) {
|
||||
geom = getInterpolationSpline(sd);
|
||||
}
|
||||
|
||||
if (geom.IsNull()) {
|
||||
throw Standard_Failure();
|
||||
}
|
||||
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(geom).Edge(), "Spline");
|
||||
}
|
||||
catch (const Standard_Failure&) {
|
||||
Base::Console().warning("ImpExpDxf - failed to create bspline\n");
|
||||
}
|
||||
break;
|
||||
try {
|
||||
Handle(Geom_BSplineCurve) geom;
|
||||
if (sd.control_points > 0) {
|
||||
geom = getSplineFromPolesAndKnots(sd);
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
break;
|
||||
else if (sd.fit_points > 0) {
|
||||
geom = getInterpolationSpline(sd);
|
||||
}
|
||||
|
||||
if (!geom.IsNull()) {
|
||||
GeometryBuilder builder(BRepBuilderAPI_MakeEdge(geom).Edge());
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Spline;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
}
|
||||
catch (const Standard_Failure&) {
|
||||
Base::Console().warning("ImpExpDxf - failed to create bspline\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,28 +941,23 @@ void ImpExpDxfRead::OnReadEllipse(const Base::Vector3d& center,
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_importMode) {
|
||||
case ImportMode::IndividualShapes:
|
||||
case ImportMode::FusedShapes: {
|
||||
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) {
|
||||
Collector->AddObject(BRepBuilderAPI_MakeEdge(ellipse).Edge(), "Ellipse");
|
||||
}
|
||||
else {
|
||||
Base::Console().warning("ImpExpDxf - ignore degenerate ellipse\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ImportMode::EditableDraft:
|
||||
case ImportMode::EditablePrimitives:
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
GeometryBuilder builder(BRepBuilderAPI_MakeEdge(ellipse).Edge());
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::Ellipse;
|
||||
}
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
|
||||
void ImpExpDxfRead::OnReadText(const Base::Vector3d& point,
|
||||
@@ -919,41 +965,36 @@ void ImpExpDxfRead::OnReadText(const Base::Vector3d& point,
|
||||
const std::string& text,
|
||||
const double rotation)
|
||||
{
|
||||
if (shouldSkipEntity()) {
|
||||
if (shouldSkipEntity() || !m_importAnnotations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that our parameters do not contain all the information needed to properly orient the
|
||||
// text. As a result the text will always appear on the XY plane
|
||||
if (m_importAnnotations) {
|
||||
auto makeText = [this, rotation, point, text, height](
|
||||
const Base::Matrix4D& transform) -> App::FeaturePython* {
|
||||
PyObject* draftModule = getDraftModule();
|
||||
if (draftModule != nullptr) {
|
||||
Base::Matrix4D localTransform;
|
||||
localTransform.rotZ(Base::toRadians(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)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
(Base::PyObjectBase*)PyObject_CallMethod(draftModule,
|
||||
"make_text",
|
||||
"sOif",
|
||||
text.c_str(),
|
||||
placement,
|
||||
0,
|
||||
height));
|
||||
Py_DECREF(placement);
|
||||
if (builtText != nullptr) {
|
||||
return dynamic_cast<App::FeaturePython*>(builtText->getDocumentObjectPtr());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
Collector->AddObject((FeaturePythonBuilder)makeText);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,82 +1020,150 @@ void ImpExpDxfRead::OnReadDimension(const Base::Vector3d& start,
|
||||
const Base::Vector3d& point,
|
||||
double /*rotation*/)
|
||||
{
|
||||
if (shouldSkipEntity()) {
|
||||
if (shouldSkipEntity() || !m_importAnnotations) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_importAnnotations) {
|
||||
auto makeDimension =
|
||||
[this, start, end, point](const Base::Matrix4D& transform) -> App::FeaturePython* {
|
||||
PyObject* draftModule = getDraftModule();
|
||||
if (draftModule != nullptr) {
|
||||
// TODO: Capture and apply OCSOrientationTransform to OCS coordinates
|
||||
// Note, some of the locations in the DXF are OCS and some are UCS, but UCS
|
||||
// doesn't mean UCS when in a block expansion, it means 'transform' So we want
|
||||
// transform*vector for "UCS" coordinates and transform*ocdCapture*vector for
|
||||
// "OCS" coordinates
|
||||
//
|
||||
// We implement the transform by mapping all the points from OCS to UCS
|
||||
// TODO: Set the Normal property to transform*(0,0,1,0)
|
||||
// TODO: Set the Direction property to transform*(the desired direction).
|
||||
// By default this is parallel to (start-end).
|
||||
PyObject* startPy = new Base::VectorPy(transform * start);
|
||||
PyObject* endPy = new Base::VectorPy(transform * end);
|
||||
PyObject* lineLocationPy = new Base::VectorPy(transform * point);
|
||||
// returns a wrapped App::FeaturePython
|
||||
auto builtDim = dynamic_cast<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());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
Collector->AddObject((FeaturePythonBuilder)makeDimension);
|
||||
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::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;
|
||||
}
|
||||
|
||||
// Polyline explosion logic is complex and calls back to other OnRead... handlers.
|
||||
// The mode switch should happen inside the final geometry creation handlers
|
||||
// (OnReadLine, OnReadArc), so this function doesn't need its own switch statement.
|
||||
// It simply acts as a dispatcher.
|
||||
|
||||
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);
|
||||
if (vertices.size() < 2 && (flags & 1) == 0) {
|
||||
return; // Not enough vertices for an open polyline
|
||||
}
|
||||
// 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");
|
||||
|
||||
BRepBuilderAPI_MakeWire wireBuilder;
|
||||
bool is_closed = ((flags & 1) != 0);
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (wireBuilder.IsDone()) {
|
||||
TopoDS_Wire wire = wireBuilder.Wire();
|
||||
GeometryBuilder builder(wire);
|
||||
|
||||
// For FusedShapes mode, we can create the object immediately.
|
||||
// For other modes, we store the builder for later processing.
|
||||
if (m_importMode == ImportMode::FusedShapes) {
|
||||
Collector->AddObject(wire, "Polyline");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_importMode == ImportMode::EditablePrimitives) {
|
||||
builder.type = GeometryBuilder::PrimitiveType::PolylineCompound;
|
||||
}
|
||||
|
||||
Collector->AddGeometry(builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,29 @@ public:
|
||||
void FinishImport() override;
|
||||
|
||||
private:
|
||||
class GeometryBuilder
|
||||
{
|
||||
public:
|
||||
// The type of primitive that a shape represents. 'None' is used for
|
||||
// non-parametric modes.
|
||||
enum class PrimitiveType
|
||||
{
|
||||
None,
|
||||
Point,
|
||||
Line,
|
||||
Circle,
|
||||
Arc,
|
||||
Ellipse,
|
||||
Spline,
|
||||
PolylineCompound
|
||||
};
|
||||
|
||||
// The raw geometric shape.
|
||||
TopoDS_Shape shape;
|
||||
// The intended parametric type for the shape.
|
||||
PrimitiveType type = PrimitiveType::None;
|
||||
};
|
||||
|
||||
ImportMode m_importMode = ImportMode::IndividualShapes;
|
||||
bool shouldSkipEntity() const
|
||||
{
|
||||
@@ -207,9 +230,7 @@ protected:
|
||||
{}
|
||||
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<GeometryBuilder>> GeometryBuilders;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Insert>> Inserts;
|
||||
};
|
||||
|
||||
@@ -259,6 +280,8 @@ protected:
|
||||
|
||||
// Called by OnReadXxxx functions to add Part objects
|
||||
virtual void AddObject(const TopoDS_Shape& shape, const char* nameBase) = 0;
|
||||
// Generic method to add a new geometry builder
|
||||
virtual void AddGeometry(const GeometryBuilder& builder) = 0;
|
||||
// Called by OnReadInsert to add App::Link or other C++-created objects
|
||||
virtual void AddObject(App::DocumentObject* obj, const char* nameBase) = 0;
|
||||
// Called by OnReadXxxx functions to add FeaturePython (draft) objects.
|
||||
@@ -288,6 +311,12 @@ protected:
|
||||
{}
|
||||
|
||||
void AddObject(const TopoDS_Shape& shape, const char* nameBase) override;
|
||||
void AddGeometry(const GeometryBuilder& builder) override
|
||||
{
|
||||
// In drawing mode, we create objects immediately based on the builder.
|
||||
// For now, this just creates simple shapes. Primitives would need more logic here.
|
||||
AddObject(builder.shape, "Shape");
|
||||
}
|
||||
void AddObject(App::DocumentObject* obj, const char* nameBase) override;
|
||||
void AddObject(FeaturePythonBuilder shapeBuilder) override;
|
||||
void AddInsert(const Base::Vector3d& point,
|
||||
@@ -351,6 +380,11 @@ protected:
|
||||
ShapesList[Reader.m_entityAttributes].push_back(shape);
|
||||
}
|
||||
|
||||
void AddGeometry(const GeometryBuilder& builder) override
|
||||
{
|
||||
ShapesList[Reader.m_entityAttributes].push_back(builder.shape);
|
||||
}
|
||||
|
||||
void AddObject(App::DocumentObject* obj, const char* nameBase) override
|
||||
{
|
||||
// A Link is not a shape to be merged, so pass to base class for standard handling.
|
||||
@@ -388,33 +422,40 @@ protected:
|
||||
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<GeometryBuilder>>& buildersList,
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Block::Insert>>& insertsList)
|
||||
: EntityCollector(reader)
|
||||
, ShapesList(shapesList)
|
||||
, FeatureBuildersList(featureBuildersList)
|
||||
, BuildersList(buildersList)
|
||||
, 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);
|
||||
// This path should no longer be taken, but is kept for compatibility.
|
||||
BuildersList[Reader.m_entityAttributes].emplace_back(GeometryBuilder(shape));
|
||||
}
|
||||
|
||||
void AddObject(App::DocumentObject* /*obj*/, const char* /*nameBase*/) override
|
||||
void AddGeometry(const GeometryBuilder& builder) override
|
||||
{
|
||||
BuildersList[Reader.m_entityAttributes].push_back(builder);
|
||||
}
|
||||
|
||||
void AddObject(App::DocumentObject* /*obj*/, const char* nameBase) override
|
||||
{
|
||||
// This path should never be executed. Links and other fully-formed DocumentObjects
|
||||
// are created from INSERT entities, not as part of a BLOCK *definition*. If this
|
||||
// warning ever appears, it indicates a logic error in the importer.
|
||||
Reader.ImportError(
|
||||
"Internal logic error: Attempted to add a DocumentObject to a block definition.");
|
||||
"Internal logic error: Attempted to add a DocumentObject ('%s') to a block "
|
||||
"definition.\n",
|
||||
nameBase);
|
||||
}
|
||||
|
||||
void AddObject(FeaturePythonBuilder /*shapeBuilder*/) override
|
||||
{
|
||||
// This path is for Draft/FeaturePython objects and is not used by the
|
||||
// primitives or shapes modes.
|
||||
}
|
||||
|
||||
void AddInsert(const Base::Vector3d& point,
|
||||
@@ -427,9 +468,7 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<TopoDS_Shape>>& ShapesList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<FeaturePythonBuilder>>&
|
||||
FeatureBuildersList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<GeometryBuilder>>& BuildersList;
|
||||
std::map<CDxfRead::CommonEntityAttributes, std::list<Block::Insert>>& InsertsList;
|
||||
};
|
||||
|
||||
@@ -507,4 +546,4 @@ protected:
|
||||
|
||||
} // namespace Import
|
||||
|
||||
#endif // IMPEXPDXF_H
|
||||
#endif // IMPEXPDXFGUI_H
|
||||
|
||||
Reference in New Issue
Block a user