freecad-cam/Mod/Draft/draftutils/params.py
2026-02-01 01:59:24 +01:00

694 lines
27 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
""" Contains a parameter observer class and parameter related functions."""
import PySide.QtCore as QtCore
import xml.etree.ElementTree as ET
import FreeCAD as App
import Draft_rc
import Arch_rc
from draftutils import init_draft_statusbar
from draftutils.translate import translate
if App.GuiUp:
import FreeCADGui as Gui
from PySide import QtWidgets
class ParamObserverDraft:
def slotParamChanged(self, param_grp, typ, entry, value):
if entry == "textheight":
_param_observer_callback_tray()
return
if entry in ("gridBorder", "gridShowHuman", "coloredGridAxes", "gridEvery",
"gridSpacing", "gridSize", "gridTransparency", "gridColor"):
_param_observer_callback_grid()
return
if entry == "DefaultAnnoScaleMultiplier":
_param_observer_callback_scalemultiplier(value)
return
if entry == "SnapBarShowOnlyDuringCommands":
_param_observer_callback_snapbar(value)
return
if entry == "DisplayStatusbarSnapWidget":
_param_observer_callback_snapwidget()
return
if entry == "DisplayStatusbarScaleWidget":
_param_observer_callback_scalewidget()
return
if entry == "snapStyle":
_param_observer_callback_snapstyle()
return
if entry == "snapcolor":
_param_observer_callback_snapcolor()
return
if entry == "patternFile":
_param_observer_callback_svg_pattern()
return
class ParamObserverView:
def slotParamChanged(self, param_grp, typ, entry, value):
if entry in ("DefaultShapeColor", "DefaultShapeLineColor", "DefaultShapeLineWidth"):
_param_observer_callback_tray()
return
if entry == "MarkerSize":
_param_observer_callback_snaptextsize()
return
def _param_observer_callback_tray():
if not hasattr(Gui, "draftToolBar"):
return
tray = Gui.draftToolBar.tray
if tray is None:
return
Gui.draftToolBar.setStyleButton()
def _param_observer_callback_scalemultiplier(value):
# value is a string.
if not value:
return
value = float(value)
if value <= 0:
return
mw = Gui.getMainWindow()
sb = mw.statusBar()
scale_widget = sb.findChild(QtWidgets.QToolBar,"draft_scale_widget")
if scale_widget is not None:
scale_label = init_draft_statusbar.scale_to_label(1 / value)
scale_widget.scaleLabel.setText(scale_label)
def _param_observer_callback_grid():
if hasattr(App, "draft_working_planes") and hasattr(Gui, "Snapper"):
try:
trackers = Gui.Snapper.trackers
for wp in App.draft_working_planes[1]:
view = wp._view
if view in trackers[0]:
i = trackers[0].index(view)
grid = trackers[1][i]
grid.pts = []
grid.reset()
grid.displayHumanFigure(wp)
grid.setAxesColor(wp)
except Exception:
pass
def _param_observer_callback_snapbar(value):
if Gui.activeWorkbench().name() not in ("DraftWorkbench", "ArchWorkbench", "BIMWorkbench"):
return
if hasattr(Gui, "Snapper"):
toolbar = Gui.Snapper.get_snap_toolbar()
if toolbar is not None:
toolbar.setVisible(value == "0") # value is a string: "0" or "1"
def _param_observer_callback_snapwidget():
if Gui.activeWorkbench().name() == "DraftWorkbench":
init_draft_statusbar.hide_draft_statusbar()
init_draft_statusbar.show_draft_statusbar()
def _param_observer_callback_scalewidget():
if Gui.activeWorkbench().name() == "DraftWorkbench":
init_draft_statusbar.hide_draft_statusbar()
init_draft_statusbar.show_draft_statusbar()
def _param_observer_callback_snapstyle():
if hasattr(Gui, "Snapper"):
Gui.Snapper.set_snap_style()
def _param_observer_callback_snapcolor():
if hasattr(Gui, "Snapper"):
tracker_list = [2, 5, 6]
for each_tracker in tracker_list:
for snap_track in Gui.Snapper.trackers[each_tracker]:
snap_track.setColor()
def _param_observer_callback_snaptextsize():
if hasattr(Gui, "Snapper"):
tracker_list = [5, 6]
for each_tracker in tracker_list:
for snap_track in Gui.Snapper.trackers[each_tracker]:
snap_track.setSize()
def _param_observer_callback_svg_pattern():
# imports have to happen here to avoid circular imports
from draftutils import utils
from draftviewproviders import view_base
utils.load_svg_patterns()
if App.ActiveDocument is None:
return
pats = list(utils.svg_patterns())
pats.sort()
pats = ["None"] + pats
data = []
for doc in App.listDocuments().values():
vobjs = []
for obj in doc.Objects:
if hasattr(obj, "ViewObject"):
vobj = obj.ViewObject
if hasattr(vobj, "Pattern") \
and hasattr(vobj, "Proxy") \
and isinstance(vobj.Proxy, view_base.ViewProviderDraft) \
and vobj.getEnumerationsOfProperty("Pattern") != pats:
vobjs.append(vobj)
if vobjs:
data.append([doc, vobjs])
if not data:
return
msg = translate("draft",
"""Do you want to update the SVG pattern options
of existing objects in all opened documents?""")
res = QtWidgets.QMessageBox.question(None, "Update SVG patterns", msg,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No)
if res == QtWidgets.QMessageBox.No:
return
for doc, vobjs in data:
doc.openTransaction("SVG pattern update")
for vobj in vobjs:
old = vobj.Pattern
if old in pats:
vobj.Pattern = pats
else:
tmp_pats = [old] + pats[1:]
tmp_pats.sort()
vobj.Pattern = ["None"] + tmp_pats
vobj.Pattern = old
doc.commitTransaction()
def _param_observer_start():
if App.GuiUp:
_param_observer_start_draft()
_param_observer_start_view()
def _param_observer_start_draft(param_grp = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")):
param_grp.AttachManager(ParamObserverDraft())
def _param_observer_start_view(param_grp = App.ParamGet("User parameter:BaseApp/Preferences/View")):
param_grp.AttachManager(ParamObserverView())
def _param_from_PrefCheckBox(widget):
value = False
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "checked": # Can be missing.
value = elem.find("bool").text == "true"
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, value
def _param_from_PrefRadioButton(widget):
return _param_from_PrefCheckBox(widget)
def _param_from_PrefComboBox(widget):
value = 0
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "currentIndex": # Can be missing.
value = int(elem.find("number").text)
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, value
def _param_from_PrefSpinBox(widget):
value = 0
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "value": # Can be missing.
value = int(elem.find("number").text)
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, value
def _param_from_PrefDoubleSpinBox(widget):
value = 0.0
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "value": # Can be missing.
value = float(elem.find("double").text)
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, value
def _param_from_PrefUnitSpinBox(widget):
value = 0.0
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "rawValue": # Can be missing.
value = float(elem.find("double").text)
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, value
def _param_from_PrefQuantitySpinBox(widget):
value = "0"
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "rawValue": # Can be missing.
value = elem.find("double").text.rstrip(".0")
elif att_name == "unit":
unit = elem.find("string").text
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
value = value + " " + unit
return path, entry, value
def _param_from_PrefColorButton(widget):
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "color":
sub = list(elem)[0]
r = int(sub.find("red").text)
g = int(sub.find("green").text)
b = int(sub.find("blue").text)
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
value = (r << 24) + (g << 16) + (b << 8) + 255
return path, entry, value
def _param_from_PrefLineEdit(widget):
value = None
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "text": # Can be missing.
value = elem.find("string").text # If text is missing value will be None here.
elif att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
if value is None:
value = ""
return path, entry, value
def _param_from_PrefFileChooser(widget):
for elem in list(widget):
if "name" in elem.keys():
att_name = elem.attrib["name"]
if att_name == "prefEntry":
entry = elem.find("cstring").text
elif att_name == "prefPath":
path = elem.find("cstring").text
return path, entry, ""
def _get_param_dictionary():
# print("Creating preferences dictionary...")
param_dict = {}
# Draft parameters that are not in the preferences:
param_dict["Mod/Draft"] = {
"AnnotationStyleEditorHeight": ("int", 450),
"AnnotationStyleEditorWidth": ("int", 450),
"CenterPlaneOnView": ("bool", False),
"ContinueMode": ("bool", False),
"CopyMode": ("bool", False),
"DefaultAnnoDisplayMode": ("int", 0),
"DefaultDisplayMode": ("int", 0),
"DefaultDrawStyle": ("int", 0),
"DefaultPrintColor": ("unsigned", 255),
"Draft_array_fuse": ("bool", False),
"Draft_array_Link": ("bool", True),
"fillmode": ("bool", True),
"GlobalMode": ("bool", False),
"GridHideInOtherWorkbenches": ("bool", True),
"HatchPatternResolution": ("int", 128),
"HatchPatternRotation": ("float", 0.0),
"HatchPatternScale": ("float", 100.0),
"labeltype": ("string", "Custom"),
"LayersManagerHeight": ("int", 320),
"LayersManagerWidth": ("int", 640),
"maxSnapEdges": ("int", 0),
"OffsetCopyMode": ("bool", False),
"Offset_OCC": ("bool", False),
"RelativeMode": ("bool", True),
"ScaleClone": ("bool", False),
"ScaleCopy": ("bool", False),
"ScaleRelative": ("bool", False),
"ScaleUniform": ("bool", False),
"snapModes": ("string", "100000000000000"),
"snapRange": ("int", 8),
"SubelementMode": ("bool", False),
"SvgLinesBlack": ("bool", True),
"useSupport": ("bool", False),
}
# Arch parameters that are not in the preferences:
param_dict["Mod/Arch"] = {
"applyConstructionStyle": ("bool", True),
"ClaimHosted": ("bool", True),
"CustomIfcSchema": ("string", ""), # importIFClegacy.py
"createIfcGroups": ("bool", False), # importIFClegacy.py
"DoorHeight": ("float", 2100.0),
"DoorPreset": ("int", 5),
"DoorSill": ("float", 0.0),
"DoorWidth": ("float", 1000.0),
"FreeLinking": ("bool", False),
"forceIfcPythonParser": ("bool", False), # importIFClegacy.py
"getStandardType": ("bool", False),
"ifcAggregateWindows": ("bool", False), # importIFClegacy.py
"ifcAsMesh": ("string", ""), # importIFClegacy.py
"IfcExportList": ("bool", False), # importIFClegacy.py
"ifcImportLayer": ("bool", True),
"ifcJoinSolids": ("bool", False), # importIFClegacy.py
"ifcMergeProfiles": ("bool", False),
"IfcScalingFactor": ("float", 1.0), # importIFClegacy.py
"ifcSeparatePlacements": ("bool", False), # importIFClegacy.py
"MultiMaterialColumnWidth0": ("int", 60),
"MultiMaterialColumnWidth1": ("int", 60),
"PanelLength": ("float", 1000.0),
"PanelThickness": ("float", 10.0),
"PanelWidth": ("float", 1000.0),
"PrecastBase": ("float", 0.0),
"PrecastChamfer": ("float", 0.0),
"PrecastDentHeight": ("float", 0.0),
"PrecastDentLength": ("float", 0.0),
"PrecastDentWidth": ("float", 0.0),
"PrecastDownLength": ("float", 0.0),
"PrecastGrooveDepth": ("float", 0.0),
"PrecastGrooveHeight": ("float", 0.0),
"PrecastGrooveSpacing": ("float", 0.0),
"PrecastHoleMajor": ("float", 0.0),
"PrecastHoleMinor": ("float", 0.0),
"PrecastHoleSpacing": ("float", 0.0),
"PrecastRiser": ("float", 0.0),
"PrecastTread": ("float", 0.0),
"ScheduleColumnWidth0": ("int", 100),
"ScheduleColumnWidth1": ("int", 100),
"ScheduleColumnWidth2": ("int", 50),
"ScheduleColumnWidth3": ("int", 100),
"ScheduleDialogHeight": ("int", 200),
"ScheduleDialogWidth": ("int", 300),
"StructureHeight": ("float", 1000.0),
"StructureLength": ("float", 100.0),
"StructurePreset": ("string", ""),
"StructureWidth": ("float", 100.0),
"swallowAdditions": ("bool", True),
"swallowSubtractions": ("bool", True),
"WallAlignment": ("int", 0),
"WallHeight": ("float", 3000.0),
"WallWidth": ("float", 200.0),
"WindowH1": ("float", 50.0),
"WindowH2": ("float", 50.0),
"WindowH3": ("float", 50.0),
"WindowHeight": ("float", 1000.0),
"WindowO1": ("float", 0.0),
"WindowO2": ("float", 50.0),
"WindowPreset": ("int", 0),
"WindowSill": ("float", 0.0),
"WindowW1": ("float", 100.0),
"WindowW2": ("float", 50.0),
"WindowWidth": ("float", 1000.0),
}
# For the Mod/Mesh parameters we do not check the preferences:
param_dict["Mod/Mesh"] = {
"MaxDeviationExport": ("float", 0.1),
}
# For the Mod/TechDraw/PAT parameters we do not check the preferences:
param_dict["Mod/TechDraw/PAT"] = {
"FilePattern": ("string", ""),
"NamePattern": ("string", "Diamant"),
}
# For the General parameters we do not check the preferences:
param_dict["General"] = {
"ToolbarIconSize": ("int", 24),
}
# For the Units parameters we do not check the preferences:
param_dict["Units"] = {
"Decimals": ("int", 2),
"UserSchema": ("int", 0),
}
# For the View parameters we do not check the preferences:
param_dict["View"] = {
"BackgroundColor": ("unsigned", 336897023),
"BackgroundColor2": ("unsigned", 859006463),
"BackgroundColor3": ("unsigned", 2543299327),
"DefaultAmbientColor": ("unsigned", 1431655935),
"DefaultEmissiveColor": ("unsigned", 255),
"DefaultShapeColor": ("unsigned", 3435980543),
"DefaultShapeLineColor": ("unsigned", 421075455),
"DefaultShapeLineWidth": ("int", 2),
"DefaultShapePointSize": ("int", 2),
"DefaultShapeShininess": ("int", 90),
"DefaultShapeTransparency": ("int", 0),
"DefaultShapeVertexColor": ("unsigned", 421075455),
"DefaultSpecularColor": ("unsigned", 2290649343),
"EnableSelection": ("bool", True),
"Gradient": ("bool", True),
"MarkerSize": ("int", 9),
"NewDocumentCameraScale": ("float", 100.0),
}
# Preferences ui files are stored in resource files.
# For the Draft Workbench: /Mod/Draft/Draft_rc.py
# For the Arch Workbench: /Mod/Arch/Arch_rc.py
for fnm in (":/ui/preferences-draft.ui",
":/ui/preferences-draftinterface.ui",
":/ui/preferences-draftsnap.ui",
":/ui/preferences-drafttexts.ui",
":/ui/preferences-draftvisual.ui",
":/ui/preferences-dwg.ui",
":/ui/preferences-dxf.ui",
":/ui/preferences-oca.ui",
":/ui/preferences-svg.ui",
":/ui/preferences-arch.ui",
":/ui/preferences-archdefaults.ui",
":/ui/preferences-dae.ui",
":/ui/preferences-ifc.ui",
":/ui/preferences-ifc-export.ui"):
# https://stackoverflow.com/questions/14750997/load-txt-file-from-resources-in-python
fd = QtCore.QFile(fnm)
if fd.open(QtCore.QIODevice.ReadOnly | QtCore.QFile.Text):
text = QtCore.QTextStream(fd).readAll()
fd.close()
else:
print("Preferences file " + fnm + " not found")
continue
# https://docs.python.org/3/library/xml.etree.elementtree.html
root = ET.fromstring(text)
# Get all preference widgets:
# pref_widgets = [wid for wid in root.iter("widget") if "Gui::Pref" in wid.attrib["class"]]
for widget in root.iter("widget"):
if "class" in widget.keys():
path = None
att_class = widget.attrib["class"]
if att_class == "Gui::PrefCheckBox":
path, entry, value = _param_from_PrefCheckBox(widget)
typ = "bool"
elif att_class == "Gui::PrefRadioButton":
path, entry, value = _param_from_PrefRadioButton(widget)
typ = "bool"
elif att_class == "Gui::PrefComboBox":
path, entry, value = _param_from_PrefComboBox(widget)
typ = "int"
elif att_class == "Gui::PrefSpinBox":
path, entry, value = _param_from_PrefSpinBox(widget)
typ = "int"
elif att_class == "Gui::PrefDoubleSpinBox":
path, entry, value = _param_from_PrefDoubleSpinBox(widget)
typ = "float"
elif att_class == "Gui::PrefUnitSpinBox":
path, entry, value = _param_from_PrefUnitSpinBox(widget)
typ = "float"
elif att_class == "Gui::PrefQuantitySpinBox":
path, entry, value = _param_from_PrefQuantitySpinBox(widget)
typ = "string"
elif att_class == "Gui::PrefColorButton":
path, entry, value = _param_from_PrefColorButton(widget)
typ = "unsigned"
elif att_class == "Gui::PrefLineEdit":
path, entry, value = _param_from_PrefLineEdit(widget)
typ = "string"
elif att_class == "Gui::PrefFileChooser":
path, entry, value = _param_from_PrefFileChooser(widget)
typ = "string"
if path is not None:
if path in param_dict:
param_dict[path][entry] = (typ, value)
else:
param_dict[path] = {entry: (typ, value)}
return param_dict
PARAM_DICT = _get_param_dictionary()
def get_param(entry, path="Mod/Draft", ret_default=False):
"""Return a stored parameter value or its default.
Parameters
----------
entry: str
Name of the parameter.
path: str, optional
Defaults to "Mod/Draft".
The path where the parameter can be found.
This string is appended to "User parameter:BaseApp/Preferences/".
ret_default: bool, optional
Defaults to `False`.
If `True`, always return the default value even if a stored value is available.
Returns
-------
bool, float, int or str (if successful) or `None`.
"""
if path not in PARAM_DICT or entry not in PARAM_DICT[path]:
print(f"draftutils.params.get_param: Unable to find '{entry}' in '{path}'")
return None
param_grp = App.ParamGet("User parameter:BaseApp/Preferences/" + path)
typ, default = PARAM_DICT[path][entry]
if ret_default:
return default
if typ == "bool":
return param_grp.GetBool(entry, default)
if typ == "float":
return param_grp.GetFloat(entry, default)
if typ == "int":
return param_grp.GetInt(entry, default)
if typ == "string":
return param_grp.GetString(entry, default)
if typ == "unsigned":
return param_grp.GetUnsigned(entry, default)
return None
def get_param_arch(entry, ret_default=False):
return get_param(entry, path="Mod/Arch", ret_default=ret_default)
def get_param_view(entry, ret_default=False):
return get_param(entry, path="View", ret_default=ret_default)
def set_param(entry, value, path="Mod/Draft"):
"""Store a parameter value.
Parameters
----------
entry: str
Name of the parameter.
value: bool, float, int or str
New value of the correct type.
path: str, optional
Defaults to "Mod/Draft".
The path where the parameter can be found.
This string is appended to "User parameter:BaseApp/Preferences/".
Returns
-------
`True` (if successful) or `False`.
"""
if path not in PARAM_DICT or entry not in PARAM_DICT[path]:
print(f"draftutils.params.set_param: Unable to find '{entry}' in '{path}'")
return False
param_grp = App.ParamGet("User parameter:BaseApp/Preferences/" + path)
typ = PARAM_DICT[path][entry][0]
ret = True
if typ == "bool":
param_grp.SetBool(entry, value)
elif typ == "float":
param_grp.SetFloat(entry, value)
elif typ == "int":
param_grp.SetInt(entry, value)
elif typ == "string":
param_grp.SetString(entry, value)
elif typ == "unsigned":
param_grp.SetUnsigned(entry, value)
else:
ret = False
return ret
def set_param_arch(entry, value):
return set_param(entry, value, path="Mod/Arch")
def set_param_view(entry, value):
return set_param(entry, value, path="View")