Merge branch 'master' into simple_convex_hull

Conflicts:
	cura/BuildVolume.py
	cura/ConvexHullDecorator.py
	cura/ConvexHullJob.py
	cura/CuraApplication.py
This commit is contained in:
Simon Edwards 2016-06-21 14:47:10 +02:00
commit fd42a43270
215 changed files with 35977 additions and 5826 deletions

View file

@ -4,7 +4,7 @@
from UM.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera
from UM.Scene.Platform import Platform
from UM.Scene.Platform import Platform as Scene_Platform
from UM.Math.Vector import Vector
from UM.Math.Quaternion import Quaternion
from UM.Math.AxisAlignedBox import AxisAlignedBox
@ -14,18 +14,26 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Logger import Logger
from UM.Preferences import Preferences
from UM.Platform import Platform
from UM.JobQueue import JobQueue
from UM.SaveFile import SaveFile
from UM.Scene.Selection import Selection
from UM.Scene.GroupDecorator import GroupDecorator
import UM.Settings.Validator
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from cura.SetParentOperation import SetParentOperation
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from . import ExtruderManager
from . import ExtrudersModel
from . import PlatformPhysics
from . import BuildVolume
from . import CameraAnimation
@ -34,20 +42,23 @@ from . import CuraActions
from . import MultiMaterialDecorator
from . import ZOffsetDecorator
from . import CuraSplashScreen
from . import MachineManagerModel
from . import ContainerSettingsModel
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtQml import qmlRegisterUncreatableType
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
import platform
import sys
import os.path
import numpy
import copy
import urllib
numpy.seterr(all="ignore")
#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
if platform.system() == "Linux": # Needed for platform.linux_distribution, which is not available on Windows and OSX
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
if platform.linux_distribution()[0] in ("Ubuntu", ): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
import ctypes
@ -55,15 +66,23 @@ if platform.system() == "Linux": # Needed for platform.linux_distribution, which
ctypes.CDLL(find_library('GL'), ctypes.RTLD_GLOBAL)
try:
from cura.CuraVersion import CuraVersion
from cura.CuraVersion import CuraVersion, CuraBuildType
except ImportError:
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
CuraBuildType = ""
class CuraApplication(QtApplication):
class ResourceTypes:
QmlFiles = Resources.UserType + 1
Firmware = Resources.UserType + 2
QualityInstanceContainer = Resources.UserType + 3
MaterialInstanceContainer = Resources.UserType + 4
VariantInstanceContainer = Resources.UserType + 5
UserInstanceContainer = Resources.UserType + 6
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
Q_ENUMS(ResourceTypes)
def __init__(self):
@ -73,7 +92,14 @@ class CuraApplication(QtApplication):
self._open_file_queue = [] # Files to open when plug-ins are loaded.
super().__init__(name = "cura", version = CuraVersion)
# Need to do this before ContainerRegistry tries to load the machines
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSettingType("extruder", int, str, UM.Settings.Validator)
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType)
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -95,31 +121,109 @@ class CuraApplication(QtApplication):
self._i18n_catalog = None
self._previous_active_tool = None
self._platform_activity = False
self._scene_boundingbox = AxisAlignedBox.Null
self._scene_bounding_box = AxisAlignedBox.Null
self._job_name = None
self._center_after_select = False
self._camera_animation = None
self._cura_actions = None
self._started = False
self.getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineChanged)
self.getMachineManager().addMachineRequested.connect(self._onAddMachineRequested)
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
Preferences.getInstance().addPreference("cura/active_machine", "")
## Add the 4 types of profiles to storage.
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
Resources.addStorageType(self.ResourceTypes.MaterialInstanceContainer, "materials")
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
# Add empty variant, material and quality containers.
# Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials.
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container._id = "empty_variant"
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container)
empty_material_container._id = "empty_material"
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container._id = "empty_quality"
empty_quality_container.addMetaDataEntry("type", "quality")
ContainerRegistry.getInstance().addContainer(empty_quality_container)
ContainerRegistry.getInstance().load()
Preferences.getInstance().addPreference("cura/active_mode", "simple")
Preferences.getInstance().addPreference("cura/recent_files", "")
Preferences.getInstance().addPreference("cura/categories_expanded", "")
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
Preferences.getInstance().addPreference("view/center_on_select", True)
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
Preferences.getInstance().setDefault("general/visible_settings", """
machine_settings
resolution
layer_height
shell
wall_thickness
top_bottom_thickness
infill
infill_sparse_density
material
material_print_temperature
material_bed_temperature
material_diameter
material_flow
retraction_enable
speed
speed_print
speed_travel
acceleration_print
acceleration_travel
jerk_print
jerk_travel
travel
cooling
cool_fan_enabled
support
support_enable
support_type
support_roof_density
platform_adhesion
adhesion_type
brim_width
raft_airgap
layer_0_z_overlap
raft_surface_layers
meshfix
blackmagic
print_sequence
dual
experimental
""".replace("\n", ";").replace(" ", ""))
JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
self.applicationShuttingDown.connect(self.saveSettings)
self._recent_files = []
files = Preferences.getInstance().getValue("cura/recent_files").split(";")
for f in files:
@ -128,6 +232,65 @@ class CuraApplication(QtApplication):
self._recent_files.append(QUrl.fromLocalFile(f))
## Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
#
# Note that the AutoSave plugin also calls this method.
def saveSettings(self):
if not self._started: # Do not do saving during application start
return
for instance in ContainerRegistry.getInstance().findInstanceContainers():
if not instance.isDirty():
continue
try:
data = instance.serialize()
except NotImplementedError:
continue
except Exception:
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
continue
file_name = urllib.parse.quote_plus(instance.getId()) + ".inst.cfg"
instance_type = instance.getMetaDataEntry("type")
path = None
if instance_type == "material":
path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
elif instance_type == "quality":
path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
elif instance_type == "user":
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
elif instance_type == "variant":
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
if path:
with SaveFile(path, "wt", -1, "utf-8") as f:
f.write(data)
for stack in ContainerRegistry.getInstance().findContainerStacks():
if not stack.isDirty():
continue
try:
data = stack.serialize()
except NotImplementedError:
continue
except Exception:
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
continue
file_name = urllib.parse.quote_plus(stack.getId()) + ".stack.cfg"
stack_type = stack.getMetaDataEntry("type", None)
path = None
if not stack_type or stack_type == "machine":
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
elif stack_type == "extruder_train":
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
if path:
with SaveFile(path, "wt", -1, "utf-8") as f:
f.write(data)
@pyqtSlot(result = QUrl)
def getDefaultPath(self):
return QUrl.fromLocalFile(os.path.expanduser("~/"))
@ -135,6 +298,8 @@ class CuraApplication(QtApplication):
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistery
def _loadPlugins(self):
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
if not hasattr(sys, "frozen"):
self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
@ -176,7 +341,7 @@ class CuraApplication(QtApplication):
Selection.selectionChanged.connect(self.onSelectionChanged)
root = controller.getScene().getRoot()
self._platform = Platform(root)
self._platform = Scene_Platform(root)
self._volume = BuildVolume.BuildVolume(root)
@ -197,7 +362,13 @@ class CuraApplication(QtApplication):
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
ExtruderManager.ExtruderManager.getInstance()
qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager",
MachineManagerModel.createMachineManagerModel)
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
self.initializeEngine()
if self._engine.rootObjects:
@ -208,6 +379,8 @@ class CuraApplication(QtApplication):
for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading.
self._openFile(file_name)
self._started = True
self.exec_()
## Handle Qt events
@ -224,6 +397,9 @@ class CuraApplication(QtApplication):
def getPrintInformation(self):
return self._print_information
## Registers objects for the QML engine to use.
#
# \param engine The QML engine.
def registerObjects(self, engine):
engine.rootContext().setContextProperty("Printer", self)
self._print_information = PrintInformation.PrintInformation()
@ -233,6 +409,21 @@ class CuraApplication(QtApplication):
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
qmlRegisterType(ExtrudersModel.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(ContainerSettingsModel.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions")
engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.ExtruderManager.getInstance())
for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles):
type_name = os.path.splitext(os.path.basename(path))[0]
if type_name in ("Cura", "Actions"):
continue
qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)
def onSelectionChanged(self):
if Selection.hasSelection():
if not self.getController().getActiveTool():
@ -267,46 +458,33 @@ class CuraApplication(QtApplication):
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_boundingbox.width.item(), 'depth': self._scene_boundingbox.depth.item(), 'height' : self._scene_boundingbox.height.item()}
return self._i18n_catalog.i18nc("@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivity(self, node = None):
count = 0
scene_boundingbox = None
scene_bounding_box = None
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode or not node.getMeshData():
continue
count += 1
if not scene_boundingbox:
scene_boundingbox = node.getBoundingBox()
if not scene_bounding_box:
scene_bounding_box = node.getBoundingBox()
else:
other_bb = node.getBoundingBox()
if other_bb is not None:
scene_boundingbox = scene_boundingbox + node.getBoundingBox()
scene_bounding_box = scene_bounding_box + node.getBoundingBox()
if not scene_boundingbox:
scene_boundingbox = AxisAlignedBox.Null
if not scene_bounding_box:
scene_bounding_box = AxisAlignedBox.Null
if repr(self._scene_boundingbox) != repr(scene_boundingbox):
self._scene_boundingbox = scene_boundingbox
if repr(self._scene_bounding_box) != repr(scene_bounding_box):
self._scene_bounding_box = scene_bounding_box
self.sceneBoundingBoxChanged.emit()
self._platform_activity = True if count > 0 else False
self.activityChanged.emit()
@pyqtSlot(str)
def setJobName(self, name):
name = os.path.splitext(name)[0] #when a file is opened using the terminal; the filename comes from _onFileLoaded and still contains its extension. This cuts the extension off if nescessary.
if self._job_name != name:
self._job_name = name
self.jobNameChanged.emit()
jobNameChanged = pyqtSignal()
@pyqtProperty(str, notify = jobNameChanged)
def jobName(self):
return self._job_name
# Remove all selected objects from the scene.
@pyqtSlot()
def deleteSelection(self):
@ -331,7 +509,7 @@ class CuraApplication(QtApplication):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if node:
@ -350,7 +528,7 @@ class CuraApplication(QtApplication):
def multiplyObject(self, object_id, count):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if node:
@ -372,7 +550,7 @@ class CuraApplication(QtApplication):
@pyqtSlot("quint64")
def centerObject(self, object_id):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if not node:
@ -385,7 +563,7 @@ class CuraApplication(QtApplication):
op = SetTransformOperation(node, Vector())
op.push()
## Delete all mesh data on the scene.
## Delete all nodes containing mesh data in the scene.
@pyqtSlot()
def deleteAll(self):
if not self.getController().getToolsEnabled():
@ -396,9 +574,9 @@ class CuraApplication(QtApplication):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue #Node that doesnt have a mesh and is not a group.
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
if nodes:
op = GroupedOperation()
@ -416,9 +594,9 @@ class CuraApplication(QtApplication):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue #Node that doesnt have a mesh and is not a group.
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
@ -438,9 +616,9 @@ class CuraApplication(QtApplication):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue #Node that doesnt have a mesh and is not a group.
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
if nodes:
@ -479,7 +657,7 @@ class CuraApplication(QtApplication):
## Get logging data of the backend engine
# \returns \type{string} Logging data
@pyqtSlot(result=str)
@pyqtSlot(result = str)
def getEngineLog(self):
log = ""
@ -509,21 +687,6 @@ class CuraApplication(QtApplication):
def expandedCategories(self):
return Preferences.getInstance().getValue("cura/categories_expanded").split(";")
@pyqtSlot(str, result = "QVariant")
def getSettingValue(self, key):
if not self.getMachineManager().getWorkingProfile():
return None
return self.getMachineManager().getWorkingProfile().getSettingValue(key)
#return self.getActiveMachine().getSettingValueByKey(key)
## Change setting by key value pair
@pyqtSlot(str, "QVariant")
def setSettingValue(self, key, value):
if not self.getMachineManager().getWorkingProfile():
return
self.getMachineManager().getWorkingProfile().setSettingValue(key, value)
@pyqtSlot()
def mergeSelected(self):
self.groupSelected()
@ -542,9 +705,10 @@ class CuraApplication(QtApplication):
# Use the previously found center of the group bounding box as the new location of the group
group_node.setPosition(group_node.getBoundingBox().center)
@pyqtSlot()
def groupSelected(self):
# Create a group-node
group_node = SceneNode()
group_decorator = GroupDecorator()
group_node.addDecorator(group_decorator)
@ -554,40 +718,34 @@ class CuraApplication(QtApplication):
group_node.setPosition(center)
group_node.setCenterPosition(center)
for node in Selection.getAllSelectedObjects():
world = node.getWorldPosition()
node.setParent(group_node)
node.setPosition(world - center)
# Move selected nodes into the group-node
Selection.applyOperation(SetParentOperation, group_node)
# Deselect individual nodes and select the group-node instead
for node in group_node.getChildren():
Selection.remove(node)
Selection.add(group_node)
@pyqtSlot()
def ungroupSelected(self):
ungrouped_nodes = []
selected_objects = Selection.getAllSelectedObjects()[:] #clone the list
selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects:
if node.callDecoration("isGroup" ):
children_to_move = []
for child in node.getChildren():
if type(child) is SceneNode:
children_to_move.append(child)
if node.callDecoration("isGroup"):
op = GroupedOperation()
for child in children_to_move:
position = child.getWorldPosition()
child.setParent(node.getParent())
child.setPosition(position - node.getParent().getWorldPosition())
child.scale(node.getScale())
child.rotate(node.getOrientation())
group_parent = node.getParent()
children = node.getChildren().copy()
for child in children:
# Set the parent of the children to the parent of the group-node
op.addOperation(SetParentOperation(child, group_parent))
# Add all individual nodes to the selection
Selection.add(child)
child.callDecoration("setConvexHull",None)
node.setParent(None)
ungrouped_nodes.append(node)
for node in ungrouped_nodes:
Selection.remove(node)
child.callDecoration("setConvexHull", None)
op.push()
# Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged
def _createSplashScreen(self):
return CuraSplashScreen.CuraSplashScreen()
@ -595,10 +753,12 @@ class CuraApplication(QtApplication):
def _onActiveMachineChanged(self):
pass
fileLoaded = pyqtSignal(str)
def _onFileLoaded(self, job):
node = job.getResult()
if node != None:
self.setJobName(os.path.basename(job.getFileName()))
self.fileLoaded.emit(job.getFileName())
node.setSelectable(True)
node.setName(os.path.basename(job.getFileName()))
op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
@ -628,14 +788,15 @@ class CuraApplication(QtApplication):
def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
job._node.setMeshData(job.getResult().getMeshData())
#job.getResult().setParent(self.getController().getScene().getRoot())
#job._node.setParent(self.getController().getScene().getRoot())
#job._node.meshDataChanged.emit(job._node)
def _openFile(self, file):
job = ReadMeshJob(os.path.abspath(file))
job.finished.connect(self._onFileLoaded)
job.start()
def _onAddMachineRequested(self):
self.requestAddPrinter.emit()
def _addProfileReader(self, profile_reader):
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
pass
def _addProfileWriter(self, profile_writer):
pass