Merge pull request #22251 from furgo16/dxf-new-import-ui-and-more
Redesign DXF import UI, add Part primitives and Draft object import modes
This commit is contained in:
@@ -430,6 +430,12 @@ PyMethodDef ApplicationPy::Methods[] = {
|
||||
"Remove all children from a group node.\n"
|
||||
"\n"
|
||||
"node : object"},
|
||||
{"suspendWaitCursor", (PyCFunction) ApplicationPy::sSuspendWaitCursor, METH_VARARGS,
|
||||
"suspendWaitCursor() -> None\n\n"
|
||||
"Temporarily suspends the application's wait cursor and event filter."},
|
||||
{"resumeWaitCursor", (PyCFunction) ApplicationPy::sResumeWaitCursor, METH_VARARGS,
|
||||
"resumeWaitCursor() -> None\n\n"
|
||||
"Resumes the application's wait cursor and event filter."},
|
||||
{nullptr, nullptr, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -1803,3 +1809,23 @@ PyObject* ApplicationPy::sSetUserEditMode(PyObject * /*self*/, PyObject *args)
|
||||
|
||||
return Py::new_reference_to(Py::Boolean(ok));
|
||||
}
|
||||
|
||||
PyObject* ApplicationPy::sSuspendWaitCursor(PyObject * /*self*/, PyObject *args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WaitCursor::suspend();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* ApplicationPy::sResumeWaitCursor(PyObject * /*self*/, PyObject *args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WaitCursor::resume();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@ public:
|
||||
static PyObject* sGetUserEditMode (PyObject *self,PyObject *args);
|
||||
static PyObject* sSetUserEditMode (PyObject *self,PyObject *args);
|
||||
|
||||
static PyObject* sSuspendWaitCursor (PyObject *self, PyObject *args);
|
||||
static PyObject* sResumeWaitCursor (PyObject *self, PyObject *args);
|
||||
|
||||
static PyMethodDef Methods[];
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -189,3 +189,16 @@ void WaitCursor::setIgnoreEvents(FilterEventsFlags flags)
|
||||
{
|
||||
WaitCursorP::getInstance()->setIgnoreEvents(flags);
|
||||
}
|
||||
|
||||
void WaitCursor::suspend()
|
||||
{
|
||||
// Calling setBusy(false) will restore the cursor and remove the event filter.
|
||||
WaitCursorP::getInstance()->setBusy(false);
|
||||
}
|
||||
|
||||
void WaitCursor::resume()
|
||||
{
|
||||
// Calling setBusy(true) will set the wait cursor and reinstall the event filter.
|
||||
// The WaitCursorP's internal state `isOn` correctly handles this call.
|
||||
WaitCursorP::getInstance()->setBusy(true);
|
||||
}
|
||||
@@ -75,6 +75,19 @@ public:
|
||||
FilterEventsFlags ignoreEvents() const;
|
||||
void setIgnoreEvents(FilterEventsFlags flags = AllEvents);
|
||||
|
||||
/**
|
||||
* @brief Suspends the wait cursor state by restoring the normal cursor
|
||||
* and removing the event filter. To be used before showing an interactive
|
||||
* dialog during a long operation.
|
||||
*/
|
||||
static void suspend();
|
||||
|
||||
/**
|
||||
* @brief Resumes the wait cursor state by setting the wait cursor
|
||||
* and reinstalling the event filter, if a WaitCursor is active.
|
||||
*/
|
||||
static void resume();
|
||||
|
||||
private:
|
||||
FilterEventsFlags filter;
|
||||
static int instances;
|
||||
|
||||
@@ -20,6 +20,7 @@ SET(Draft_SRCS_base
|
||||
SET(Draft_import
|
||||
importAirfoilDAT.py
|
||||
importDXF.py
|
||||
DxfImportDialog.py
|
||||
importDWG.py
|
||||
importOCA.py
|
||||
importSVG.py
|
||||
|
||||
116
src/Mod/Draft/DxfImportDialog.py
Normal file
116
src/Mod/Draft/DxfImportDialog.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
class DxfImportDialog:
|
||||
"""
|
||||
A controller class that creates, manages, and shows the DXF import dialog.
|
||||
"""
|
||||
def __init__(self, entity_counts, parent=None):
|
||||
# Step 1: Load the UI from the resource file. This returns a new QDialog instance.
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(":/ui/preferences-dxf-import.ui")
|
||||
|
||||
# Now, all widgets like "label_Summary" are attributes of self.dialog
|
||||
|
||||
self.entity_counts = entity_counts
|
||||
self.total_entities = sum(entity_counts.values())
|
||||
|
||||
self.setup_ui()
|
||||
self.connect_signals()
|
||||
self.load_settings_and_set_initial_state()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Perform initial UI setup."""
|
||||
self.dialog.label_Summary.setText(f"File contains approximately {self.total_entities} geometric entities.")
|
||||
self.dialog.label_Warning.hide()
|
||||
|
||||
def connect_signals(self):
|
||||
"""Connect signals from the dialog's widgets to our methods."""
|
||||
buttonBox = self.dialog.findChild(QtGui.QDialogButtonBox, "buttonBox")
|
||||
if buttonBox:
|
||||
# Connect to our custom slots INSTEAD of the dialog's built-in ones
|
||||
buttonBox.accepted.connect(self.on_accept)
|
||||
buttonBox.rejected.connect(self.on_reject)
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: OK and Cancel buttons connected.\n")
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning("DxfImportDialog: Could not find buttonBox!\n")
|
||||
|
||||
self.dialog.radio_ImportAs_Draft.toggled.connect(self.update_warning_label)
|
||||
self.dialog.radio_ImportAs_Primitives.toggled.connect(self.update_warning_label)
|
||||
self.dialog.radio_ImportAs_Shapes.toggled.connect(self.update_warning_label)
|
||||
self.dialog.radio_ImportAs_Fused.toggled.connect(self.update_warning_label)
|
||||
|
||||
def on_accept(self):
|
||||
"""Custom slot to debug the OK button click."""
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: 'OK' button clicked. Calling self.dialog.accept().\n")
|
||||
# Manually call the original slot
|
||||
self.dialog.accept()
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: self.dialog.accept() has been called.\n")
|
||||
|
||||
def on_reject(self):
|
||||
"""Custom slot to debug the Cancel button click."""
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: 'Cancel' button clicked. Calling self.dialog.reject().\n")
|
||||
# Manually call the original slot
|
||||
self.dialog.reject()
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: self.dialog.reject() has been called.\n")
|
||||
|
||||
def load_settings_and_set_initial_state(self):
|
||||
"""Load saved preferences and set the initial state of the dialog."""
|
||||
hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
||||
|
||||
mode = hGrp.GetInt("DxfImportMode", 2)
|
||||
|
||||
if mode == 0:
|
||||
self.dialog.radio_ImportAs_Draft.setChecked(True)
|
||||
elif mode == 1:
|
||||
self.dialog.radio_ImportAs_Primitives.setChecked(True)
|
||||
elif mode == 3:
|
||||
self.dialog.radio_ImportAs_Fused.setChecked(True)
|
||||
else:
|
||||
self.dialog.radio_ImportAs_Shapes.setChecked(True)
|
||||
|
||||
is_legacy = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
if is_legacy:
|
||||
self.dialog.radio_ImportAs_Primitives.setEnabled(False)
|
||||
self.dialog.radio_ImportAs_Draft.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Shapes.setEnabled(True)
|
||||
self.dialog.radio_ImportAs_Fused.setEnabled(True)
|
||||
else:
|
||||
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)
|
||||
|
||||
self.update_warning_label()
|
||||
|
||||
def update_warning_label(self):
|
||||
"""Updates the warning label based on selection and entity count."""
|
||||
self.dialog.label_Warning.hide()
|
||||
current_mode = self.get_selected_mode()
|
||||
|
||||
if self.total_entities > 5000 and (current_mode == 0 or current_mode == 1):
|
||||
self.dialog.label_Warning.setText("Warning: Importing over 5000 entities as editable objects can be very slow.")
|
||||
self.dialog.label_Warning.show()
|
||||
elif self.total_entities > 20000 and current_mode == 2:
|
||||
self.dialog.label_Warning.setText("Warning: Importing over 20,000 entities as individual shapes may be slow.")
|
||||
self.dialog.label_Warning.show()
|
||||
|
||||
def exec_(self):
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: Calling self.dialog.exec_()...\n")
|
||||
result = self.dialog.exec_()
|
||||
FreeCAD.Console.PrintLog("DxfImportDialog: self.dialog.exec_() returned with result: {}\n".format(result))
|
||||
# QDialog.Accepted is usually 1, Rejected is 0.
|
||||
FreeCAD.Console.PrintLog("(Note: QDialog.Accepted = {}, QDialog.Rejected = {})\n".format(QtGui.QDialog.Accepted, QtGui.QDialog.Rejected))
|
||||
return result
|
||||
|
||||
def get_selected_mode(self):
|
||||
"""Return the integer value of the selected import mode."""
|
||||
if self.dialog.radio_ImportAs_Draft.isChecked(): return 0
|
||||
if self.dialog.radio_ImportAs_Primitives.isChecked(): return 1
|
||||
if self.dialog.radio_ImportAs_Fused.isChecked(): return 3
|
||||
if self.dialog.radio_ImportAs_Shapes.isChecked(): return 2
|
||||
return 2
|
||||
|
||||
def get_show_dialog_again(self):
|
||||
"""Return True if the dialog should be shown next time."""
|
||||
return not self.dialog.checkBox_ShowDialogAgain.isChecked()
|
||||
@@ -185,6 +185,7 @@
|
||||
<file>ui/preferences-draftvisual.ui</file>
|
||||
<file>ui/preferences-dwg.ui</file>
|
||||
<file>ui/preferences-dxf.ui</file>
|
||||
<file>ui/preferences-dxf-import.ui</file>
|
||||
<file>ui/preferences-oca.ui</file>
|
||||
<file>ui/preferences-svg.ui</file>
|
||||
<file>ui/TaskPanel_CircularArray.ui</file>
|
||||
|
||||
154
src/Mod/Draft/Resources/ui/preferences-dxf-import.ui
Normal file
154
src/Mod/Draft/Resources/ui/preferences-dxf-import.ui
Normal file
@@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DxfImportDialog</class>
|
||||
<widget class="QDialog" name="DxfImportDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>280</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>DXF Import</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_ImportAs">
|
||||
<property name="title">
|
||||
<string>Import As</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_ImportAs">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_ImportAs_Draft">
|
||||
<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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Editable Draft objects</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_ImportAs_Primitives">
|
||||
<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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Editable Part primitives</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_ImportAs_Shapes">
|
||||
<property name="toolTip">
|
||||
<string>Creates a non-parametric shape for each DXF entity. Block definitions are
|
||||
imported as reusable objects (Part Compounds) and instances become `App::Link`
|
||||
objects, maintaining the block structure. Good for referencing and measuring.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Individual Part shapes (recommended)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_ImportAs_Fused">
|
||||
<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>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fused Part shapes (fastest)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_Summary">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_Summary">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Summary">
|
||||
<property name="text">
|
||||
<string>File summary</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Warning">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #c00;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Warning</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_ShowDialogAgain">
|
||||
<property name="text">
|
||||
<string>Do not show this dialog again</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -6,27 +6,28 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>649</width>
|
||||
<height>800</height>
|
||||
<width>600</width>
|
||||
<height>880</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>DXF</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_Main">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<widget class="QGroupBox" name="groupBox_General">
|
||||
<property name="title">
|
||||
<string>General options</string>
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_General">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfShowDialog">
|
||||
<property name="toolTip">
|
||||
<string>This preferences dialog will be shown when importing/ exporting DXF files</string>
|
||||
<string>If checked, this preferences dialog will be shown each time you import or export
|
||||
a DXF file.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show this dialog when importing and exporting</string>
|
||||
<string>Show the importer dialog when importing a file</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@@ -42,14 +43,10 @@
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfUseLegacyImporter">
|
||||
<property name="toolTip">
|
||||
<string>Python importer is used, otherwise the newer C++ is used.
|
||||
Note: C++ importer is faster, but is not as featureful yet</string>
|
||||
<string>Use the legacy Python importer. This importer is more feature-complete but slower and requires an external library.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use legacy Python importer</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
<string>Use legacy importer</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfUseLegacyImporter</cstring>
|
||||
@@ -62,11 +59,10 @@ Note: C++ importer is faster, but is not as featureful yet</string>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfUseLegacyExporter">
|
||||
<property name="toolTip">
|
||||
<string>Python exporter is used, otherwise the newer C++ is used.
|
||||
Note: C++ exporter is faster, but is not as featureful yet</string>
|
||||
<string>Use the legacy Python exporter. This exporter is more feature-complete but slower and requires an external library.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use legacy Python exporter</string>
|
||||
<string>Use legacy exporter</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfUseLegacyExporter</cstring>
|
||||
@@ -80,227 +76,160 @@ Note: C++ exporter is faster, but is not as featureful yet</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_1">
|
||||
<widget class="QGroupBox" name="groupBox_AutoUpdate">
|
||||
<property name="title">
|
||||
<string>Automatic update (legacy importer/exporter only)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_AutoUpdate">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfAllowDownload">
|
||||
<property name="toolTip">
|
||||
<string>Allow FreeCAD to download the Python converter for DXF import and export.
|
||||
You can also do this manually by installing the "dxf_library" workbench
|
||||
from the Addon Manager.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow FreeCAD to automatically download and update the DXF libraries</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfAllowDownload</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfAllowDownload">
|
||||
<property name="toolTip">
|
||||
<string>If checked, FreeCAD is allowed to download and update the Python libraries
|
||||
required by the legacy importer. This can also be done manually by installing
|
||||
the 'dxf_library' addon from the Addon Manager.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow FreeCAD to automatically download and update the DXF libraries</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfAllowDownload</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="GroupBox_2">
|
||||
<widget class="QGroupBox" name="groupBox_ImportAs">
|
||||
<property name="title">
|
||||
<string>Import options</string>
|
||||
<string>Import As</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_ImportAs">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_ImporterMissing">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Draft">
|
||||
<property name="enabled">
|
||||
<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. </string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Some options are not yet available for the new importer</string>
|
||||
<string>Editable Draft objects (highest fidelity, slowest)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfImportAsDraft</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
<property name="prefRadioButtonGroup" stdset="0">
|
||||
<string>DxfImportMode</string>
|
||||
</property>
|
||||
<property name="prefRadioButtonValue" stdset="0">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Import">
|
||||
<property name="text">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxftext">
|
||||
<property name="toolTip">
|
||||
<string>If unchecked, texts and mtexts won't be imported</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Texts and dimensions</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxftext</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfImportPoints">
|
||||
<property name="toolTip">
|
||||
<string>If unchecked, points won't be imported</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>points</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfImportPoints</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxflayout">
|
||||
<property name="toolTip">
|
||||
<string>If checked, paper space objects will be imported too</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Layouts</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxflayout</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfstarblocks">
|
||||
<property name="toolTip">
|
||||
<string>If you want the non-named blocks (beginning with a *) to be imported too</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>*blocks</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfstarblocks</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Primitives">
|
||||
<property name="enabled">
|
||||
<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 and Part workbench integration.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Editable Part primitives (high fidelity, slower)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfImportAsPrimitives</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
<property name="prefRadioButtonGroup" stdset="0">
|
||||
<string>DxfImportMode</string>
|
||||
</property>
|
||||
<property name="prefRadioButtonValue" stdset="0">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Create">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefRadioButton" name="radioButton_dxfCreatePart">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Only standard Part objects will be created (fastest)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Simple Part shapes</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfCreatePart</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefRadioButton" name="radioButton_dxfCreateDraft">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Parametric Draft objects will be created whenever possible</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Draft objects</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfCreateDraft</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefRadioButton" name="radioButton_dxfCreateSketch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Sketches will be created whenever possible</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sketches</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfCreateSketch</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Shapes">
|
||||
<property name="toolTip">
|
||||
<string>Creates a non-parametric shape for each DXF entity. Block definitions are
|
||||
imported as reusable objects (Part Compounds) and instances become `App::Link`
|
||||
objects, maintaining the block structure. Good for referencing and measuring.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Individual Part shapes (balanced, recommended)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfImportAsShapes</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
<property name="prefRadioButtonGroup" stdset="0">
|
||||
<string>DxfImportMode</string>
|
||||
</property>
|
||||
<property name="prefRadioButtonValue" stdset="0">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<widget class="Gui::PrefRadioButton" name="radio_ImportAs_Fused">
|
||||
<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 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>dxfImportAsFused</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
<property name="prefRadioButtonGroup" stdset="0">
|
||||
<string>DxfImportMode</string>
|
||||
</property>
|
||||
<property name="prefRadioButtonValue" stdset="0">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_ImportSettings">
|
||||
<property name="title">
|
||||
<string>Import Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_ImportSettings">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_Scaling">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_dxfScaling">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scale factor to apply to imported files</string>
|
||||
<string>Global scaling factor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -320,13 +249,13 @@ from the Addon Manager.</string>
|
||||
<item>
|
||||
<widget class="Gui::PrefDoubleSpinBox" name="spinBox_dxfScaling">
|
||||
<property name="toolTip">
|
||||
<string>Scale factor to apply to DXF files on import.
|
||||
The factor is the conversion between the unit of your DXF file and millimeters.
|
||||
Example: for files in millimeters: 1, in centimeters: 10,
|
||||
in meters: 1000, in inches: 25.4, in feet: 304.8</string>
|
||||
<string>Scale factor to apply to DXF files on import. The factor is the conversion
|
||||
between the DXF file's unit and millimeters. Example: for files in
|
||||
millimeters: 1, in centimeters: 10, in meters: 1000, in inches: 25.4,
|
||||
in feet: 304.8</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>12</number>
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999999.999998999992386</double>
|
||||
@@ -345,16 +274,126 @@ Example: for files in millimeters: 1, in centimeters: 10,
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_ImportContent">
|
||||
<property name="text">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_Import">
|
||||
<item row="0" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxftext">
|
||||
<property name="toolTip">
|
||||
<string>If checked, text, mtext, and dimension entities will be imported as Draft objects</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Texts and dimensions</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxftext</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfImportPoints">
|
||||
<property name="toolTip">
|
||||
<string>If checked, point entities will be imported</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Points</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfImportPoints</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxflayout">
|
||||
<property name="toolTip">
|
||||
<string>If checked, entities from the paper space will also be imported. By default,
|
||||
only model space is imported</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Paper space objects</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxflayout</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfstarblocks">
|
||||
<property name="toolTip">
|
||||
<string>If checked, anonymous blocks (whose names begin with *) will also be imported.
|
||||
These are often used for hatches and dimensions</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Anonymous blocks (*-blocks)</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfstarblocks</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_importDxfHatches">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>If checked, the boundaries of hatch objects will be imported as closed wires.
|
||||
(Legacy importer only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hatch boundaries</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>importDxfHatches</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Appearance">
|
||||
<property name="text">
|
||||
<string>Appearance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_Appearance">
|
||||
<item row="0" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfGetOriginalColors">
|
||||
<property name="toolTip">
|
||||
<string>Colors will set as specified in the DXF file whenever possible.
|
||||
Otherwise default colors will be applied.</string>
|
||||
<string>If checked, colors will be set as specified in the DXF file whenever
|
||||
possible. Otherwise, default FreeCAD colors are applied</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use colors from the DXF file</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfGetOriginalColors</cstring>
|
||||
</property>
|
||||
@@ -363,63 +402,14 @@ Otherwise default colors will be applied.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_joingeometry">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FreeCAD will try to join coincident objects into wires.
|
||||
Note that this can take a while!</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Join geometry</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>joingeometry</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_groupLayers">
|
||||
<property name="toolTip">
|
||||
<string>Objects from the same layers will be joined into Part Compounds,
|
||||
turning the display faster, but making them less easily editable.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge layer contents into blocks</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>groupLayers</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfStdSize">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Imported texts will get the standard Draft Text size,
|
||||
instead of the size they have in the DXF document</string>
|
||||
<string>If checked, imported texts will get the standard Draft text size, instead of
|
||||
the size defined in the DXF document. (Legacy importer only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use standard font size for texts</string>
|
||||
@@ -435,61 +425,42 @@ instead of the size they have in the DXF document</string>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfUseDraftVisGroups">
|
||||
<property name="toolTip">
|
||||
<string>If this is checked, DXF layers will be imported as Draft Layers</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use layers</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfUseDraftVisGroups</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QLabel" name="label_AdvancedProcessing">
|
||||
<property name="text">
|
||||
<string>Advanced processing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_importDxfHatches">
|
||||
<layout class="QGridLayout" name="gridLayout_Advanced">
|
||||
<item row="0" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_joingeometry">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Hatches will be converted into simple wires</string>
|
||||
<string>If checked, the legacy importer will attempt to join coincident geometric
|
||||
objects into wires. This can be slow for large files. (Legacy importer only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Import hatch boundaries as wires</string>
|
||||
<string>Join geometry</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>importDxfHatches</cstring>
|
||||
<cstring>joingeometry</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_renderPolylineWidth">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>If polylines have a width defined, they will be rendered
|
||||
as closed wires with correct width</string>
|
||||
<string>If checked, polylines that have a width property will be rendered as faces
|
||||
representing that width. (Legacy importer only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Render polylines with width</string>
|
||||
@@ -502,31 +473,39 @@ as closed wires with correct width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfCreateSketch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>If checked, the legacy importer will attempt to create Sketcher objects
|
||||
instead of Draft or Part objects. This overrides the 'Import As' setting</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create sketches</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>dxfCreateSketch</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Draft</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<widget class="QGroupBox" name="groupBox_ExportOptions">
|
||||
<property name="title">
|
||||
<string>Export options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_Export">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_ExporterMissing">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Some options are not yet available for the new exporter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_Discretize">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_DiscretizeEllipses">
|
||||
<property name="toolTip">
|
||||
@@ -598,7 +577,7 @@ If it is set to '0' the whole spline is treated as a straight segment.</string>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_13">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_Export3D">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfmesh">
|
||||
<property name="enabled">
|
||||
@@ -621,7 +600,7 @@ If it is set to '0' the whole spline is treated as a straight segment.</string>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_14">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_ExportTechDraw">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfExportBlocks">
|
||||
<property name="toolTip">
|
||||
@@ -645,7 +624,7 @@ This might fail for post DXF R12 templates.</string>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_15">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_Project">
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_dxfproject">
|
||||
<property name="enabled">
|
||||
@@ -671,21 +650,20 @@ This might fail for post DXF R12 templates.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::PrefCheckBox</class>
|
||||
@@ -693,82 +671,18 @@ This might fail for post DXF R12 templates.</string>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefDoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<class>Gui::PrefRadioButton</class>
|
||||
<extends>QRadioButton</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefRadioButton</class>
|
||||
<extends>QRadioButton</extends>
|
||||
<class>Gui::PrefDoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>label_Create</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>radioButton_dxfCreatePart</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>radioButton_dxfCreateDraft</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>radioButton_dxfCreateSketch</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
@@ -785,6 +699,22 @@ This might fail for post DXF R12 templates.</string>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>checkBox_renderPolylineWidth</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
@@ -820,7 +750,55 @@ This might fail for post DXF R12 templates.</string>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyImporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>checkBox_renderPolylineWidth</receiver>
|
||||
<receiver>checkBox_dxfCreateSketch</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfCreateSketch</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>groupBox_ImportAs</receiver>
|
||||
<slot>setDisabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyExporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>checkBox_dxfmesh</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_dxfUseLegacyExporter</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>checkBox_dxfproject</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
|
||||
@@ -69,6 +69,7 @@ from draftobjects.dimension import _Dimension
|
||||
from draftutils import params
|
||||
from draftutils import utils
|
||||
from draftutils.utils import pyopen
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
gui = FreeCAD.GuiUp
|
||||
draftui = None
|
||||
@@ -78,6 +79,13 @@ if gui:
|
||||
draftui = FreeCADGui.draftToolBar
|
||||
except (AttributeError, NameError):
|
||||
draftui = None
|
||||
try:
|
||||
from draftviewproviders.view_base import ViewProviderDraft
|
||||
from draftviewproviders.view_wire import ViewProviderWire
|
||||
from draftviewproviders.view_dimension import ViewProviderLinearDimension
|
||||
except ImportError:
|
||||
ViewProviderDraft = None
|
||||
ViewProviderWire = None
|
||||
from draftutils.translate import translate
|
||||
from PySide import QtWidgets
|
||||
else:
|
||||
@@ -2789,12 +2797,109 @@ 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()
|
||||
|
||||
# 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
|
||||
|
||||
# --- Post-processing step ---
|
||||
if not use_legacy and newly_created_objects:
|
||||
draft_postprocessor = DxfDraftPostProcessor(doc, newly_created_objects, import_mode)
|
||||
draft_postprocessor.run()
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
----------
|
||||
@@ -2803,96 +2908,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.
|
||||
"""
|
||||
readPreferences()
|
||||
total_start_time = time.perf_counter()
|
||||
doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=None)
|
||||
|
||||
if dxfUseLegacyImporter:
|
||||
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)
|
||||
else:
|
||||
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)
|
||||
|
||||
total_end_time = time.perf_counter()
|
||||
if stats:
|
||||
reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
Draft.convert_draft_texts() # convert annotations to Draft texts
|
||||
doc.recompute()
|
||||
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.
|
||||
"""
|
||||
readPreferences()
|
||||
total_start_time = time.perf_counter()
|
||||
try:
|
||||
doc = FreeCAD.getDocument(docname)
|
||||
except NameError:
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
FreeCAD.setActiveDocument(docname)
|
||||
if dxfUseLegacyImporter:
|
||||
getDXFlibs()
|
||||
if dxfReader:
|
||||
processdxf(doc, filename)
|
||||
else:
|
||||
errorDXFLib(gui)
|
||||
else:
|
||||
stats = None
|
||||
if gui:
|
||||
import ImportGui
|
||||
stats = ImportGui.readDXF(filename)
|
||||
else:
|
||||
import Import
|
||||
stats = Import.readDXF(filename)
|
||||
doc, stats, start_time, end_time = _import_dxf_file(filename, doc_name=docname)
|
||||
|
||||
total_end_time = time.perf_counter()
|
||||
if stats:
|
||||
reporter = DxfImportReporter(filename, stats, total_end_time - total_start_time)
|
||||
reporter.report_to_console()
|
||||
if doc and stats:
|
||||
reporter = DxfImportReporter(filename, stats, end_time - start_time)
|
||||
reporter.report_to_console()
|
||||
|
||||
Draft.convert_draft_texts() # convert annotations to Draft texts
|
||||
doc.recompute()
|
||||
|
||||
def getShapes(filename):
|
||||
"""Read a DXF file, and return a list of shapes from its contents.
|
||||
@@ -4181,41 +4228,77 @@ def readPreferences():
|
||||
-----
|
||||
Use local variables, not global variables.
|
||||
"""
|
||||
# reading parameters
|
||||
if gui and params.get_param("dxfShowDialog"):
|
||||
FreeCADGui.showPreferencesByName("Import-Export", ":/ui/preferences-dxf.ui")
|
||||
global dxfCreatePart, dxfCreateDraft, dxfCreateSketch
|
||||
global dxfDiscretizeCurves, dxfStarBlocks
|
||||
global dxfMakeBlocks, dxfJoin, dxfRenderPolylineWidth
|
||||
global dxfImportTexts, dxfImportLayouts
|
||||
global dxfImportPoints, dxfImportHatches, dxfUseStandardSize
|
||||
global dxfGetColors, dxfUseDraftVisGroups
|
||||
global dxfMakeFaceMode, dxfBrightBackground, dxfDefaultColor
|
||||
global dxfUseLegacyImporter, dxfExportBlocks, dxfScaling
|
||||
global dxfUseLegacyExporter
|
||||
dxfCreatePart = params.get_param("dxfCreatePart")
|
||||
dxfCreateDraft = params.get_param("dxfCreateDraft")
|
||||
dxfCreateSketch = params.get_param("dxfCreateSketch")
|
||||
dxfDiscretizeCurves = params.get_param("DiscretizeEllipses")
|
||||
dxfStarBlocks = params.get_param("dxfstarblocks")
|
||||
dxfMakeBlocks = params.get_param("groupLayers")
|
||||
dxfJoin = params.get_param("joingeometry")
|
||||
dxfRenderPolylineWidth = params.get_param("renderPolylineWidth")
|
||||
dxfImportTexts = params.get_param("dxftext")
|
||||
dxfImportLayouts = params.get_param("dxflayout")
|
||||
dxfImportPoints = params.get_param("dxfImportPoints")
|
||||
dxfImportHatches = params.get_param("importDxfHatches")
|
||||
dxfUseStandardSize = params.get_param("dxfStdSize")
|
||||
dxfGetColors = params.get_param("dxfGetOriginalColors")
|
||||
dxfUseDraftVisGroups = params.get_param("dxfUseDraftVisGroups")
|
||||
dxfMakeFaceMode = params.get_param("MakeFaceMode")
|
||||
dxfUseLegacyImporter = params.get_param("dxfUseLegacyImporter")
|
||||
dxfUseLegacyExporter = params.get_param("dxfUseLegacyExporter")
|
||||
global dxfDiscretizeCurves, dxfStarBlocks, dxfMakeBlocks, dxfJoin, dxfRenderPolylineWidth
|
||||
global dxfImportTexts, dxfImportLayouts, dxfImportPoints, dxfImportHatches, dxfUseStandardSize
|
||||
global dxfGetColors, dxfUseDraftVisGroups, dxfMakeFaceMode, dxfBrightBackground, dxfDefaultColor
|
||||
global dxfUseLegacyImporter, dxfExportBlocks, dxfScaling, dxfUseLegacyExporter
|
||||
|
||||
# Use the direct C++ API via Python for all parameter access
|
||||
hGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
|
||||
|
||||
dxfUseLegacyImporter = hGrp.GetBool("dxfUseLegacyImporter", False)
|
||||
|
||||
# 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: # dxfCreateSketch overrides the import mode for the legacy importer
|
||||
dxfCreatePart = False
|
||||
dxfCreateDraft = False
|
||||
dxfMakeBlocks = 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)
|
||||
dxfStarBlocks = hGrp.GetBool("dxfstarblocks", False)
|
||||
dxfJoin = hGrp.GetBool("joingeometry", False)
|
||||
dxfRenderPolylineWidth = hGrp.GetBool("renderPolylineWidth", False)
|
||||
dxfImportTexts = hGrp.GetBool("dxftext", False)
|
||||
dxfImportLayouts = hGrp.GetBool("dxflayout", False)
|
||||
dxfImportPoints = hGrp.GetBool("dxfImportPoints", True)
|
||||
dxfImportHatches = hGrp.GetBool("importDxfHatches", False)
|
||||
dxfUseStandardSize = hGrp.GetBool("dxfStdSize", False)
|
||||
dxfGetColors = hGrp.GetBool("dxfGetOriginalColors", True)
|
||||
dxfUseDraftVisGroups = hGrp.GetBool("dxfUseDraftVisGroups", True)
|
||||
dxfMakeFaceMode = hGrp.GetBool("MakeFaceMode", False)
|
||||
dxfUseLegacyExporter = hGrp.GetBool("dxfUseLegacyExporter", False)
|
||||
dxfExportBlocks = hGrp.GetBool("dxfExportBlocks", True)
|
||||
dxfScaling = hGrp.GetFloat("dxfScaling", 1.0)
|
||||
|
||||
dxfBrightBackground = isBrightBackground()
|
||||
dxfDefaultColor = getColor()
|
||||
dxfExportBlocks = params.get_param("dxfExportBlocks")
|
||||
dxfScaling = params.get_param("dxfScaling")
|
||||
|
||||
|
||||
class DxfImportReporter:
|
||||
"""Formats and reports statistics from a DXF import process."""
|
||||
@@ -4346,3 +4429,515 @@ class DxfImportReporter:
|
||||
"""
|
||||
output_string = self.to_console_string()
|
||||
FCC.PrintMessage(output_string)
|
||||
|
||||
|
||||
class DxfDraftPostProcessor:
|
||||
"""
|
||||
Handles the post-processing of DXF files imported as Part objects,
|
||||
converting them into fully parametric Draft objects while preserving
|
||||
the block and layer hierarchy.
|
||||
"""
|
||||
def __init__(self, doc, new_objects, import_mode):
|
||||
self.doc = doc
|
||||
self.all_imported_objects = new_objects
|
||||
self.import_mode = import_mode
|
||||
self.all_originals_to_delete = set()
|
||||
self.newly_created_draft_objects = []
|
||||
|
||||
def _categorize_objects(self):
|
||||
"""
|
||||
Scans newly created objects from the C++ importer and categorizes them.
|
||||
"""
|
||||
block_definitions = {}
|
||||
for group_name in ["_BlockDefinitions", "_UnreferencedBlocks"]:
|
||||
block_group = self.doc.getObject(group_name)
|
||||
if block_group:
|
||||
for block_def_obj in block_group.Group:
|
||||
if block_def_obj.isValid() and block_def_obj.isDerivedFrom("Part::Compound"):
|
||||
block_definitions[block_def_obj] = [
|
||||
child for child in block_def_obj.Links
|
||||
if child.isValid()
|
||||
]
|
||||
|
||||
all_block_internal_objects_set = set()
|
||||
for block_def, children in block_definitions.items():
|
||||
all_block_internal_objects_set.add(block_def)
|
||||
all_block_internal_objects_set.update(children)
|
||||
|
||||
top_level_geometry = []
|
||||
placeholders = []
|
||||
for obj in self.all_imported_objects:
|
||||
if not obj.isValid() or obj in all_block_internal_objects_set:
|
||||
continue
|
||||
|
||||
if obj.isDerivedFrom("App::FeaturePython") and hasattr(obj, "DxfEntityType"):
|
||||
placeholders.append(obj)
|
||||
elif obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("App::Link"):
|
||||
top_level_geometry.append(obj)
|
||||
|
||||
return block_definitions, top_level_geometry, placeholders
|
||||
|
||||
def _create_draft_object_from_part(self, part_obj):
|
||||
"""
|
||||
Converts an intermediate Part object (from C++ importer) to a final Draft object,
|
||||
ensuring correct underlying C++ object typing and property management.
|
||||
Returns a tuple: (new_draft_object, type_string) or (None, None).
|
||||
"""
|
||||
if self.import_mode != 0:
|
||||
# In non-Draft modes, do not convert geometry. Return it as is.
|
||||
return part_obj, "KeptAsIs"
|
||||
|
||||
# Skip invalid objects or objects that are block definitions themselves (their
|
||||
# links/children will be converted)
|
||||
if not part_obj.isValid() or \
|
||||
(part_obj.isDerivedFrom("Part::Compound") and hasattr(part_obj, "Links")):
|
||||
return None, None
|
||||
|
||||
new_obj = None
|
||||
obj_type_str = None # Will be set based on converted type
|
||||
|
||||
# Handle specific Part primitives (created directly by C++ importer as Part::Line,
|
||||
# Part::Circle, Part::Vertex) These C++ primitives (Part::Line, Part::Circle) inherently
|
||||
# have Shape and Placement. Part::Vertex is special, handled separately below.
|
||||
if part_obj.isDerivedFrom("Part::Line"):
|
||||
# Input `part_obj` is Part::Line. Create a Part::Part2DObjectPython as the
|
||||
# Python-extensible base for Draft Line. Part::Part2DObjectPython (via Part::Feature)
|
||||
# inherently has Shape and Placement, and supports .Proxy.
|
||||
new_obj = self.doc.addObject("Part::Part2DObjectPython",
|
||||
self.doc.getUniqueObjectName("Line"))
|
||||
# Transfer the TopoDS_Shape from the original Part::Line to the new object's Shape
|
||||
# property.
|
||||
new_obj.Shape = part_obj.Shape
|
||||
Draft.Wire(new_obj) # Attach the Python proxy. It will find Shape, Placement.
|
||||
|
||||
# Manually transfer the parametric data from the Part::Line primitive
|
||||
# to the new Draft.Wire's 'Points' property.
|
||||
start_point = FreeCAD.Vector(part_obj.X1.Value, part_obj.Y1.Value, part_obj.Z1.Value)
|
||||
end_point = FreeCAD.Vector(part_obj.X2.Value, part_obj.Y2.Value, part_obj.Z2.Value)
|
||||
new_obj.Points = [start_point, end_point]
|
||||
|
||||
obj_type_str = "Line"
|
||||
|
||||
elif part_obj.isDerivedFrom("Part::Circle"):
|
||||
# Input `part_obj` is Part::Circle. Create a Part::Part2DObjectPython.
|
||||
new_obj = self.doc.addObject("Part::Part2DObjectPython",
|
||||
self.doc.getUniqueObjectName("Circle"))
|
||||
# Transfer the TopoDS_Shape from the original Part::Circle. This needs to happen
|
||||
# *before* proxy attach.
|
||||
new_obj.Shape = part_obj.Shape
|
||||
|
||||
# Attach the Python proxy
|
||||
# This call will add properties like Radius, FirstAngle, LastAngle to new_obj.
|
||||
Draft.Circle(new_obj)
|
||||
|
||||
# Transfer data *after* proxy attachment.
|
||||
# Now that Draft.Circle(new_obj) has run and added the properties, we can assign values
|
||||
# to them.
|
||||
# Part::Circle has Radius, Angle1, Angle2 properties.
|
||||
# Draft.Circle proxy uses FirstAngle and LastAngle instead of Angle1 and Angle2.
|
||||
if hasattr(part_obj, 'Radius'):
|
||||
new_obj.Radius = FreeCAD.Units.Quantity(part_obj.Radius.Value, "mm")
|
||||
|
||||
# Calculate and transfer angles
|
||||
if hasattr(part_obj, 'Angle1') and hasattr(part_obj, 'Angle2'):
|
||||
start_angle, end_angle = self._get_canonical_angles(
|
||||
part_obj.Angle1.Value,
|
||||
part_obj.Angle2.Value,
|
||||
part_obj.Radius.Value
|
||||
)
|
||||
|
||||
new_obj.FirstAngle = FreeCAD.Units.Quantity(start_angle, "deg")
|
||||
new_obj.LastAngle = FreeCAD.Units.Quantity(end_angle, "deg")
|
||||
|
||||
# Determine the final object type string based on the canonical angles
|
||||
is_full_circle = (abs(new_obj.FirstAngle.Value - 0.0) < 1e-7 and
|
||||
abs(new_obj.LastAngle.Value - 360.0) < 1e-7)
|
||||
|
||||
obj_type_str = "Circle" if is_full_circle else "Arc"
|
||||
|
||||
|
||||
elif part_obj.isDerivedFrom("Part::Vertex"): # Input `part_obj` is Part::Vertex (C++ primitive for a point location).
|
||||
# For Draft.Point, the proxy expects an App::FeaturePython base.
|
||||
new_obj = self.doc.addObject("App::FeaturePython", self.doc.getUniqueObjectName("Point"))
|
||||
new_obj.addExtension("Part::AttachExtensionPython") # Needed to provide Placement for App::FeaturePython.
|
||||
# Transfer Placement explicitly from the original Part::Vertex.
|
||||
if hasattr(part_obj, 'Placement'):
|
||||
new_obj.Placement = part_obj.Placement
|
||||
else:
|
||||
new_obj.Placement = FreeCAD.Placement()
|
||||
Draft.Point(new_obj) # Attach the Python proxy.
|
||||
obj_type_str = "Point"
|
||||
|
||||
elif part_obj.isDerivedFrom("Part::Ellipse"):
|
||||
# Determine if it's a full ellipse or an arc
|
||||
# The span check handles cases like (0, 360) or (-180, 180)
|
||||
span = abs(part_obj.Angle2.Value - part_obj.Angle1.Value)
|
||||
is_full_ellipse = abs(span % 360.0) < 1e-6
|
||||
|
||||
if is_full_ellipse:
|
||||
# Create the C++ base object that has .Shape and .Placement.
|
||||
new_obj = self.doc.addObject("Part::Part2DObjectPython", self.doc.getUniqueObjectName("Ellipse"))
|
||||
|
||||
# Attach the parametric Draft.Ellipse Python proxy.
|
||||
Draft.Ellipse(new_obj)
|
||||
|
||||
# Transfer the parametric properties from the imported primitive to the new Draft
|
||||
# object. The proxy will handle recomputing the shape.
|
||||
new_obj.MajorRadius = part_obj.MajorRadius
|
||||
new_obj.MinorRadius = part_obj.MinorRadius
|
||||
new_obj.Placement = part_obj.Placement
|
||||
|
||||
obj_type_str = "Ellipse"
|
||||
else:
|
||||
# Fallback for elliptical arcs.
|
||||
|
||||
new_obj = self.doc.addObject("Part::Part2DObjectPython", self.doc.getUniqueObjectName("EllipticalArc"))
|
||||
Draft.Wire(new_obj) # Attach proxy.
|
||||
|
||||
# Re-create geometry at the origin using parametric properties.
|
||||
# Convert degrees back to radians for the geometry kernel.
|
||||
center_at_origin = FreeCAD.Vector(0, 0, 0)
|
||||
geom = Part.Ellipse(center_at_origin, part_obj.MajorRadius.Value, part_obj.MinorRadius.Value)
|
||||
shape_at_origin = geom.toShape(math.radians(part_obj.Angle1.Value),
|
||||
math.radians(part_obj.Angle2.Value))
|
||||
|
||||
# Assign the un-transformed shape and the separate placement.
|
||||
new_obj.Shape = shape_at_origin
|
||||
new_obj.Placement = part_obj.Placement
|
||||
obj_type_str = "Shape"
|
||||
|
||||
# --- Handle generic Part::Feature objects (from C++ importer, wrapping TopoDS_Shapes like Wires, Splines, Ellipses) ---
|
||||
elif part_obj.isDerivedFrom("Part::Feature"): # Input `part_obj` is a generic Part::Feature (from C++ importer).
|
||||
shape = part_obj.Shape # This is the underlying TopoDS_Shape (Wire, Edge, Compound, Face etc.).
|
||||
if not shape.isValid():
|
||||
return None, None
|
||||
|
||||
# Determine specific Draft object type based on the ShapeType of the TopoDS_Shape.
|
||||
if shape.ShapeType == "Wire": # If the TopoDS_Shape is a Wire (from DXF POLYLINE).
|
||||
# Create a Part::Part2DObjectPython as the Python-extensible base for Draft Wire.
|
||||
new_obj = self.doc.addObject("Part::Part2DObjectPython", self.doc.getUniqueObjectName("Wire"))
|
||||
new_obj.Shape = shape # Transfer the TopoDS_Wire from the original Part::Feature.
|
||||
Draft.Wire(new_obj) # Attach Python proxy. It will find Shape, Placement.
|
||||
|
||||
# Check if all segments of the wire are straight lines.
|
||||
# If so, we can safely populate the .Points property to make it parametric.
|
||||
# Otherwise, we do nothing, leaving it as a non-parametric but geometrically correct shape.
|
||||
is_all_lines = True
|
||||
|
||||
for edge in shape.Edges:
|
||||
if edge.Curve.TypeId == "Part::GeomLine":
|
||||
continue # This is a straight segment
|
||||
else:
|
||||
is_all_lines = False
|
||||
break # Found a curve, no need to check further
|
||||
|
||||
if is_all_lines and shape.OrderedVertexes:
|
||||
# All segments are straight, so we can make it an editable wire
|
||||
points = [v.Point for v in shape.OrderedVertexes]
|
||||
new_obj.Points = points
|
||||
|
||||
new_obj.Closed = shape.isClosed() # Transfer specific properties expected by Draft.Wire.
|
||||
obj_type_str = "Wire"
|
||||
|
||||
# Fallback for other Part::Feature shapes (e.g., 3DFACE, SOLID, or unsupported Edge types).
|
||||
else: # If the TopoDS_Shape is not a recognized primitive (e.g., Compound, Face, Solid).
|
||||
# Wrap it in a Part::FeaturePython to allow Python property customization if needed.
|
||||
new_obj = self.doc.addObject("Part::FeaturePython", self.doc.getUniqueObjectName("Shape"))
|
||||
new_obj.addExtension("Part::AttachExtensionPython") # Add extension for Placement for App::FeaturePython.
|
||||
new_obj.Shape = shape # Assign the TopoDS_Shape from the original Part::Feature.
|
||||
# Explicitly set Placement for App::FeaturePython.
|
||||
if hasattr(part_obj, 'Placement'):
|
||||
new_obj.Placement = part_obj.Placement
|
||||
else:
|
||||
new_obj.Placement = FreeCAD.Placement()
|
||||
# No specific Draft proxy for generic "Shape", but it's Python extensible.
|
||||
obj_type_str = "Shape"
|
||||
|
||||
# --- Handle App::Link objects (block instances from C++ importer) ---
|
||||
elif part_obj.isDerivedFrom("App::Link"): # Input `part_obj` is an App::Link.
|
||||
# App::Link objects are already suitable as a base for Draft.Clone/Array links.
|
||||
# They natively have Placement and Link properties, and support .Proxy.
|
||||
new_obj = part_obj # Reuse the object directly.
|
||||
obj_type_str = "Link"
|
||||
|
||||
# --- Handle App::FeaturePython placeholder objects (Text, Dimension from C++ importer) ---
|
||||
elif part_obj.isDerivedFrom("App::FeaturePython"): # Input `part_obj` is an App::FeaturePython placeholder.
|
||||
# These are specific placeholders the C++ importer created (`DxfEntityType` property).
|
||||
# They are processed later in `_create_from_placeholders` to become proper Draft.Text/Dimension objects.
|
||||
return None, None # Don't process them here; let the dedicated function handle them.
|
||||
|
||||
# --- Final Common Steps for Newly Created Draft Objects ---
|
||||
if new_obj:
|
||||
new_obj.Label = part_obj.Label # Always transfer label.
|
||||
|
||||
# If `new_obj` was freshly created (not `part_obj` reused), and `part_obj` had a Placement,
|
||||
# ensure `new_obj`'s Placement is correctly set from `part_obj`.
|
||||
# For `Part::*` types, Placement is set implicitly by the `addObject` call based on their `Shape`.
|
||||
# For `App::FeaturePython` (like for Point and generic Shape fallback), explicit assignment is needed.
|
||||
if new_obj is not part_obj:
|
||||
if hasattr(part_obj, "Placement") and hasattr(new_obj, "Placement"):
|
||||
new_obj.Placement = part_obj.Placement
|
||||
elif not hasattr(new_obj, "Placement"):
|
||||
# This should ideally not happen with the corrected logic above.
|
||||
FCC.PrintWarning(f"Created object '{new_obj.Label}' of type '{obj_type_str}' does not have a 'Placement' property even after intended setup. This is unexpected.\n")
|
||||
|
||||
# Add the original object (from C++ importer) to the list for deletion.
|
||||
if new_obj is not part_obj:
|
||||
self.all_originals_to_delete.add(part_obj)
|
||||
|
||||
return new_obj, obj_type_str
|
||||
|
||||
# If no conversion could be made (e.g., unsupported DXF entity not falling into a handled case),
|
||||
# mark original for deletion and return None.
|
||||
self.all_originals_to_delete.add(part_obj)
|
||||
FCC.PrintWarning(f"DXF Post-Processor: Failed to convert object '{part_obj.Label}'. Discarding.\n")
|
||||
return None, None
|
||||
|
||||
def _parent_object_to_layer(self, new_obj, original_obj):
|
||||
"""Finds the correct layer from the original object and parents the new object to it."""
|
||||
if hasattr(original_obj, "OriginalLayer"):
|
||||
layer_name = original_obj.OriginalLayer
|
||||
|
||||
found_layers = self.doc.getObjectsByLabel(layer_name)
|
||||
|
||||
layer_obj = None
|
||||
if found_layers:
|
||||
for l_obj in found_layers:
|
||||
if Draft.get_type(l_obj) == 'Layer':
|
||||
layer_obj = l_obj
|
||||
break
|
||||
|
||||
if layer_obj:
|
||||
layer_obj.Proxy.addObject(layer_obj, new_obj)
|
||||
else:
|
||||
FCC.PrintWarning(f"DXF Post-Processor: Could not find a valid Draft Layer with label '{layer_name}' for object '{new_obj.Label}'.\n")
|
||||
|
||||
def _create_and_parent_geometry(self, intermediate_obj):
|
||||
"""High-level helper to convert, name, and parent a single geometric object."""
|
||||
new_draft_obj, obj_type_str = self._create_draft_object_from_part(intermediate_obj)
|
||||
if new_draft_obj:
|
||||
label = intermediate_obj.Label
|
||||
if not label or "__Feature" in label:
|
||||
label = self.doc.getUniqueObjectName(obj_type_str)
|
||||
new_draft_obj.Label = label
|
||||
self._parent_object_to_layer(new_draft_obj, intermediate_obj)
|
||||
self.newly_created_draft_objects.append(new_draft_obj)
|
||||
else:
|
||||
FCC.PrintWarning(f"DXF Post-Processor: Failed to convert object '{intermediate_obj.Label}'. Discarding.\n")
|
||||
return new_draft_obj
|
||||
|
||||
def _create_from_placeholders(self, placeholders):
|
||||
"""Creates final Draft objects from text/dimension placeholders."""
|
||||
if not placeholders:
|
||||
return
|
||||
|
||||
for placeholder in placeholders:
|
||||
if not placeholder.isValid():
|
||||
continue
|
||||
new_obj = None
|
||||
try:
|
||||
if placeholder.DxfEntityType == "DIMENSION":
|
||||
# 1. Create the base object and attach the proxy, which adds the needed properties.
|
||||
dim = self.doc.addObject("App::FeaturePython", "Dimension")
|
||||
_Dimension(dim)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
ViewProviderLinearDimension(dim.ViewObject)
|
||||
|
||||
# 2. Get the transformation from the placeholder's Placement property.
|
||||
plc = placeholder.Placement
|
||||
|
||||
# 3. Transform the defining points from the placeholder's local coordinate system
|
||||
# into the world coordinate system.
|
||||
p_start = plc.multVec(placeholder.Start)
|
||||
p_end = plc.multVec(placeholder.End)
|
||||
p_dimline = plc.multVec(placeholder.Dimline)
|
||||
|
||||
# 4. Assign these new, transformed points to the final dimension object.
|
||||
dim.Start = p_start
|
||||
dim.End = p_end
|
||||
dim.Dimline = p_dimline
|
||||
|
||||
# Do NOT try to set dim.Placement, as it does not exist.
|
||||
|
||||
new_obj = dim
|
||||
|
||||
# Check for and apply the dimension type (horizontal, vertical, etc.)
|
||||
# This information is now plumbed through from the C++ importer.
|
||||
if hasattr(placeholder, "DxfDimensionType"):
|
||||
# The lower bits of the type flag define the dimension's nature.
|
||||
# 0 = Rotated, Horizontal, or Vertical
|
||||
# 1 = Aligned
|
||||
# Other values are for angular, diameter, etc., not handled here.
|
||||
dim_type = placeholder.DxfDimensionType & 0x0F
|
||||
|
||||
# A type of 0 indicates that the dimension is projected. The
|
||||
# projection direction is given by its rotation angle.
|
||||
if dim_type == 0 and hasattr(placeholder, "DxfRotation"):
|
||||
angle = placeholder.DxfRotation.Value # Angle is in radians
|
||||
|
||||
# The Direction property on a Draft.Dimension controls its
|
||||
# projection. Setting it here ensures the ViewProvider
|
||||
# will draw it correctly as horizontal, vertical, or rotated.
|
||||
direction_vector = FreeCAD.Vector(math.cos(angle), math.sin(angle), 0)
|
||||
dim.Direction = direction_vector
|
||||
|
||||
elif placeholder.DxfEntityType == "TEXT":
|
||||
text_obj = Draft.make_text(placeholder.Text)
|
||||
text_obj.Placement = placeholder.Placement
|
||||
if FreeCAD.GuiUp:
|
||||
text_obj.addProperty("App::PropertyFloat", "DxfTextHeight", "Internal")
|
||||
text_obj.DxfTextHeight = placeholder.DxfTextHeight
|
||||
new_obj = text_obj
|
||||
|
||||
if new_obj:
|
||||
new_obj.Label = placeholder.Label
|
||||
self._parent_object_to_layer(new_obj, placeholder)
|
||||
self.newly_created_draft_objects.append(new_obj)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning(f"Could not create Draft object from placeholder '{placeholder.Label}': {e}\n")
|
||||
|
||||
self.all_originals_to_delete.update(placeholders)
|
||||
|
||||
def _apply_gui_styles(self):
|
||||
"""Attaches correct ViewProviders and styles to new Draft objects."""
|
||||
if not FreeCAD.GuiUp:
|
||||
return
|
||||
|
||||
# We style all newly created Draft objects, which are collected in this list.
|
||||
# This now includes block children, top-level geometry, and placeholders.
|
||||
all_objects_to_style = self.newly_created_draft_objects
|
||||
|
||||
for obj in all_objects_to_style:
|
||||
if obj.isValid() and hasattr(obj, "ViewObject") and hasattr(obj, "Proxy"):
|
||||
try:
|
||||
proxy_name = obj.Proxy.__class__.__name__
|
||||
if proxy_name in ("Wire", "Line"):
|
||||
if ViewProviderWire: ViewProviderWire(obj.ViewObject)
|
||||
elif proxy_name == "Circle":
|
||||
if ViewProviderDraft: ViewProviderDraft(obj.ViewObject)
|
||||
elif proxy_name == "Text":
|
||||
if hasattr(obj, "DxfTextHeight"):
|
||||
obj.ViewObject.FontSize = obj.DxfTextHeight * TEXTSCALING
|
||||
except Exception as e:
|
||||
FCC.PrintWarning(f"Failed to set ViewProvider for {obj.Name}: {e}\n")
|
||||
|
||||
def _delete_objects_in_batch(self):
|
||||
"""Safely deletes all objects marked for removal."""
|
||||
if not self.all_originals_to_delete:
|
||||
return
|
||||
for obj in self.all_originals_to_delete:
|
||||
if obj.isValid() and self.doc.getObject(obj.Name) is not None:
|
||||
try:
|
||||
if not obj.isDerivedFrom("App::DocumentObjectGroup") and not obj.isDerivedFrom("App::Link"):
|
||||
self.doc.removeObject(obj.Name)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning(f"Failed to delete object '{getattr(obj, 'Label', obj.Name)}': {e}\n")
|
||||
|
||||
def _cleanup_organizational_groups(self):
|
||||
"""Removes empty organizational groups after processing."""
|
||||
for group_name in ["_BlockDefinitions", "_UnreferencedBlocks"]:
|
||||
group = self.doc.getObject(group_name)
|
||||
if group and not group.Group:
|
||||
try:
|
||||
self.doc.removeObject(group.Name)
|
||||
except Exception as e:
|
||||
FCC.PrintWarning(
|
||||
"DXF Post-Processor: Could not remove temporary group "
|
||||
f"'{group.Name}': {e}\n"
|
||||
)
|
||||
|
||||
def _get_canonical_angles(self, start_angle_deg, end_angle_deg, radius_mm):
|
||||
"""
|
||||
Calculates canonical start and end angles for a Draft Arc/Circle that are
|
||||
both geometrically equivalent to the input and syntactically valid for
|
||||
FreeCAD's App::PropertyAngle, which constrains values to [-360, 360].
|
||||
|
||||
This is necessary because the C++ importer may provide angles outside this
|
||||
range (e.g., end_angle > 360) to unambiguously define an arc's span and
|
||||
distinguish between minor and major arcs. This function finds an
|
||||
equivalent angle pair that respects the C++ constraints while preserving
|
||||
the original geometry (span and direction).
|
||||
"""
|
||||
# Calculate the original angular span.
|
||||
span = end_angle_deg - start_angle_deg
|
||||
|
||||
# Handle degenerate and full-circle cases first.
|
||||
# Case: A zero-radius, zero-span arc is a point.
|
||||
if abs(radius_mm) < 1e-9 and abs(span) < 1e-9:
|
||||
return 0.0, 0.0
|
||||
|
||||
# A span that is a multiple of 360 degrees is a full circle.
|
||||
# Use a tolerance for floating point inaccuracies.
|
||||
if abs(span % 360.0) < 1e-6 and abs(span) > 1e-7:
|
||||
# Return the canonical representation for a full circle in Draft.
|
||||
return 0.0, 360.0
|
||||
|
||||
# Normalize the start angle to a canonical [0, 360] range.
|
||||
canonical_start = start_angle_deg % 360.0
|
||||
if canonical_start < 0:
|
||||
canonical_start += 360.0
|
||||
|
||||
# Calculate the geometrically correct end angle based on the preserved span.
|
||||
canonical_end = canonical_start + span
|
||||
|
||||
# Find a valid representation within the [-360, 360] constraints.
|
||||
# We can shift both start and end by multiples of 360 without changing the geometry.
|
||||
# This "slides" the angular window until it fits within the allowed range.
|
||||
# This handles cases where the calculated end > 360 or start is very negative.
|
||||
while canonical_start > 360.0 or canonical_end > 360.0:
|
||||
canonical_start -= 360.0
|
||||
canonical_end -= 360.0
|
||||
|
||||
while canonical_start < -360.0 or canonical_end < -360.0:
|
||||
canonical_start += 360.0
|
||||
canonical_end += 360.0
|
||||
|
||||
# At this point, the pair (canonical_start, canonical_end) is both
|
||||
# geometrically correct and should be valid for App::PropertyAngle.
|
||||
return canonical_start, canonical_end
|
||||
|
||||
def run(self):
|
||||
"""Executes the entire post-processing workflow."""
|
||||
FCC.PrintMessage("\n--- DXF DRAFT POST-PROCESSING ---\n")
|
||||
if not self.all_imported_objects:
|
||||
return
|
||||
|
||||
self.doc.openTransaction("DXF Post-processing")
|
||||
try:
|
||||
block_defs, top_geo, placeholders = self._categorize_objects()
|
||||
|
||||
# Process geometry inside block definitions
|
||||
for block_def_obj, original_children in block_defs.items():
|
||||
new_draft_children = [self._create_and_parent_geometry(child) for child in original_children]
|
||||
block_def_obj.Links = [obj for obj in new_draft_children if obj]
|
||||
self.all_originals_to_delete.update(set(original_children) - set(new_draft_children))
|
||||
|
||||
# Process top-level geometry
|
||||
converted_top_geo = []
|
||||
for part_obj in top_geo:
|
||||
new_obj = self._create_and_parent_geometry(part_obj)
|
||||
if new_obj:
|
||||
converted_top_geo.append(new_obj)
|
||||
self.all_originals_to_delete.update(set(top_geo) - set(converted_top_geo))
|
||||
|
||||
# Process placeholders like Text and Dimensions
|
||||
self._create_from_placeholders(placeholders)
|
||||
|
||||
# Perform all deletions at once
|
||||
self._delete_objects_in_batch()
|
||||
|
||||
except Exception as e:
|
||||
self.doc.abortTransaction()
|
||||
FCC.PrintError(f"Aborting DXF post-processing due to an error: {e}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return
|
||||
finally:
|
||||
self.doc.commitTransaction()
|
||||
|
||||
self._apply_gui_styles()
|
||||
self._cleanup_organizational_groups()
|
||||
|
||||
self.doc.recompute()
|
||||
FCC.PrintMessage("--- Draft post-processing finished. ---\n")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,15 @@ class BRepAdaptor_Curve;
|
||||
|
||||
namespace Import
|
||||
{
|
||||
|
||||
enum class ImportMode
|
||||
{
|
||||
EditableDraft,
|
||||
EditablePrimitives,
|
||||
IndividualShapes,
|
||||
FusedShapes
|
||||
};
|
||||
|
||||
class ImportExport ImpExpDxfRead: public CDxfRead
|
||||
{
|
||||
public:
|
||||
@@ -52,7 +61,7 @@ public:
|
||||
{
|
||||
Py_XDECREF(DraftModule);
|
||||
}
|
||||
|
||||
static std::map<std::string, int> PreScan(const std::string& filepath);
|
||||
void StartImport() override;
|
||||
|
||||
Py::Object getStatsAsPyObject();
|
||||
@@ -91,6 +100,7 @@ public:
|
||||
void OnReadDimension(const Base::Vector3d& start,
|
||||
const Base::Vector3d& end,
|
||||
const Base::Vector3d& point,
|
||||
int dimensionType,
|
||||
double rotation) override;
|
||||
void OnReadPolyline(std::list<VertexInfo>& /*vertices*/, int flags) override;
|
||||
|
||||
@@ -108,6 +118,31 @@ 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,
|
||||
PolylineFlattened, // Polyline imported as a simple Part::Feature with a TopoDS_Wire
|
||||
PolylineParametric // Polyline imported as a Part::Compound of Part primitives
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
// This entity is in paper space, and the user setting says to ignore it.
|
||||
@@ -127,6 +162,8 @@ private:
|
||||
void ComposeBlocks();
|
||||
void ComposeParametricBlock(const std::string& blockName, std::set<std::string>& composed);
|
||||
void ComposeFlattenedBlock(const std::string& blockName, std::set<std::string>& composed);
|
||||
Part::Compound* createParametricPolylineCompound(const TopoDS_Wire& wire, const char* name);
|
||||
Part::Feature* createFlattenedPolylineFeature(const TopoDS_Wire& wire, const char* name);
|
||||
|
||||
protected:
|
||||
PyObject* getDraftModule()
|
||||
@@ -143,6 +180,10 @@ protected:
|
||||
CDxfRead::Layer*
|
||||
MakeLayer(const std::string& name, ColorIndex_t color, std::string&& lineType) override;
|
||||
|
||||
TopoDS_Wire BuildWireFromPolyline(std::list<VertexInfo>& vertices, int flags);
|
||||
void CreateFlattenedPolyline(const TopoDS_Wire& wire, const char* name);
|
||||
void CreateParametricPolyline(const TopoDS_Wire& wire, const char* name);
|
||||
|
||||
// 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
|
||||
@@ -197,9 +238,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;
|
||||
};
|
||||
|
||||
@@ -211,6 +250,7 @@ private:
|
||||
App::DocumentObjectGroup* m_unreferencedBlocksGroup = nullptr;
|
||||
App::Document* document;
|
||||
std::string m_optionSource;
|
||||
void _addOriginalLayerProperty(App::DocumentObject* obj);
|
||||
|
||||
protected:
|
||||
friend class DrawingEntityCollector;
|
||||
@@ -249,6 +289,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.
|
||||
@@ -278,6 +320,7 @@ protected:
|
||||
{}
|
||||
|
||||
void AddObject(const TopoDS_Shape& shape, const char* nameBase) override;
|
||||
void AddGeometry(const GeometryBuilder& builder) override;
|
||||
void AddObject(App::DocumentObject* obj, const char* nameBase) override;
|
||||
void AddObject(FeaturePythonBuilder shapeBuilder) override;
|
||||
void AddInsert(const Base::Vector3d& point,
|
||||
@@ -341,6 +384,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.
|
||||
@@ -370,7 +418,6 @@ protected:
|
||||
|
||||
private:
|
||||
const EntityCollector* previousEntityCollector;
|
||||
const eEntityMergeType_t previousMmergeOption;
|
||||
};
|
||||
#endif
|
||||
class BlockDefinitionCollector: public EntityCollector
|
||||
@@ -379,33 +426,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,
|
||||
@@ -418,9 +472,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;
|
||||
};
|
||||
|
||||
@@ -498,4 +550,4 @@ protected:
|
||||
|
||||
} // namespace Import
|
||||
|
||||
#endif // IMPEXPDXF_H
|
||||
#endif // IMPEXPDXFGUI_H
|
||||
|
||||
@@ -2331,7 +2331,7 @@ bool CDxfRead::ReadDimension()
|
||||
switch ((eDimensionType_t)dimensionType) {
|
||||
case eLinear:
|
||||
case eAligned:
|
||||
OnReadDimension(start, end, linePosition, Base::toRadians(rotation));
|
||||
OnReadDimension(start, end, linePosition, dimensionType, Base::toRadians(rotation));
|
||||
break;
|
||||
default:
|
||||
UnsupportedFeature("Dimension type '%d'", dimensionType);
|
||||
|
||||
@@ -938,6 +938,7 @@ public:
|
||||
virtual void OnReadDimension(const Base::Vector3d& /*start*/,
|
||||
const Base::Vector3d& /*end*/,
|
||||
const Base::Vector3d& /*point*/,
|
||||
int /*dimensionType*/,
|
||||
double /*rotation*/)
|
||||
{}
|
||||
virtual void OnReadPolyline(std::list<VertexInfo>& /*vertices*/, int /*flags*/)
|
||||
|
||||
@@ -94,6 +94,7 @@ public:
|
||||
add_keyword_method("insert",
|
||||
&Module::insert,
|
||||
"insert(string,string) -- Insert the file into the given document.");
|
||||
add_varargs_method("preScanDxf", &Module::preScanDxf, "preScanDxf(filepath) -> dict");
|
||||
add_varargs_method("readDXF",
|
||||
&Module::readDXF,
|
||||
"readDXF(filename,[document,ignore_errors,option_source]): Imports a "
|
||||
@@ -112,6 +113,26 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Py::Object preScanDxf(const Py::Tuple& args)
|
||||
{
|
||||
char* filepath_char = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "et", "utf-8", &filepath_char)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
std::string filepath(filepath_char);
|
||||
PyMem_Free(filepath_char);
|
||||
|
||||
#include <Mod/Import/App/dxf/ImpExpDxf.h>
|
||||
|
||||
std::map<std::string, int> counts = Import::ImpExpDxfRead::PreScan(filepath);
|
||||
|
||||
Py::Dict result;
|
||||
for (const auto& pair : counts) {
|
||||
result.setItem(Py::String(pair.first), Py::Long(pair.second));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Py::Object importOptions(const Py::Tuple& args)
|
||||
{
|
||||
char* Name {};
|
||||
|
||||
Reference in New Issue
Block a user