Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Thomas-Karl Pietrowski 2016-03-14 09:33:29 +01:00
commit 7a9e19555a
209 changed files with 53682 additions and 16653 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.pyc
*kdev*
*.kate-swp
__pycache__
docs/html
*.lprof

View File

@ -9,6 +9,20 @@ set(URANIUM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/../uranium/scripts" CACHE DIRECTORY
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
# Macro needed to list all sub-directory of a directory.
# There is no function in cmake as far as I know.
# Found at: http://stackoverflow.com/a/7788165
MACRO(SUBDIRLIST result curdir)
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
SET(dirlist "")
FOREACH(child ${children})
IF(IS_DIRECTORY ${curdir}/${child})
LIST(APPEND dirlist ${child})
ENDIF()
ENDFOREACH()
SET(${result} ${dirlist})
ENDMACRO()
if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
# Extract Strings
add_custom_target(extract-messages ${URANIUM_SCRIPTS_DIR}/extract-messages ${CMAKE_SOURCE_DIR} cura)
@ -24,36 +38,16 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
# build directory to the source resources directory. This is mostly a convenience
# during development, normally you want to simply use the install target to install
# the files along side the rest of the application.
add_custom_target(copy-translations)
#TODO: Properly install the built files. This should be done after we move the applications out of the Uranium repo.
set(languages
en
x-test
ru
fr
de
it
es
fi
pl
cs
bg
)
SUBDIRLIST(languages ${CMAKE_SOURCE_DIR}/resources/i18n/)
foreach(lang ${languages})
file(GLOB po_files resources/i18n/${lang}/*.po)
foreach(file ${po_files})
string(REGEX REPLACE ".*/(.*).po" "${lang}/\\1.mo" mofile ${file})
add_custom_command(TARGET translations POST_BUILD COMMAND mkdir ARGS -p ${lang} COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ARGS ${file} -o ${mofile})
file(GLOB po_files ${CMAKE_SOURCE_DIR}/resources/i18n/${lang}/*.po)
foreach(po_file ${po_files})
string(REGEX REPLACE ".*/(.*).po" "${CMAKE_BINARY_DIR}/resources/i18n/${lang}/LC_MESSAGES/\\1.mo" mo_file ${po_file})
add_custom_command(TARGET translations POST_BUILD COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/resources/i18n/${lang}/LC_MESSAGES/ COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ARGS ${po_file} -o ${mo_file} -f)
endforeach()
file(GLOB mo_files ${CMAKE_BINARY_DIR}/${lang}/*.mo)
foreach(file ${mo_files})
add_custom_command(TARGET copy-translations POST_BUILD COMMAND mkdir ARGS -p ${CMAKE_SOURCE_DIR}/resources/i18n/${lang}/LC_MESSAGES/ COMMAND cp ARGS ${file} ${CMAKE_SOURCE_DIR}/resources/i18n/${lang}/LC_MESSAGES/ COMMENT "Copying ${file}...")
endforeach()
install(FILES ${mo_files} DESTINATION ${CMAKE_INSTALL_DATADIR}/uranium/resources/i18n/${lang}/LC_MESSAGES/)
endforeach()
install(DIRECTORY ${CMAKE_BINARY_DIR}/resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
endif()
endif()
@ -66,6 +60,9 @@ if(NOT APPLE AND NOT WIN32)
install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages/cura)
install(FILES cura.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES cura.sharedmimeinfo
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages/
RENAME cura.xml )
else()
install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)

View File

@ -1,13 +1,15 @@
[Desktop Entry]
Version=15.06.01
Name=Cura
Name[de]=Cura
GenericName=3D Printing Software
GenericName[de]=3D-Druck-Software
Comment=
Exec=/usr/bin/cura_app.py
TryExec=/usr/bin/cura_app.py
Exec=/usr/bin/cura_app.py %f
TryExec=/usr/bin/cura_app.py %f
Icon=/usr/share/cura/resources/images/cura-icon.png
Terminal=false
Type=Application
MimeType=application/sla
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png
Categories=Graphics;
Keywords=3D;Printing;

22
cura.sharedmimeinfo Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/vnd.ms-3mfdocument">
<comment>3D Manufacturing Format Document</comment>
<icon name="unknown"/>
<glob-deleteall/>
<glob pattern="*.3mf"/>
</mime-type>
<mime-type type="application/sla">
<comment>Computer-aided design and manufacturing format</comment>
<icon name="unknown"/>
<glob-deleteall/>
<glob pattern="*.stl"/>
</mime-type>
<mime-type type="application/prs.wavefront-obj">
<sub-class-of type="text/plain"/>
<comment>Wavefront 3D Object file</comment>
<icon name="unknown"/>
<glob-deleteall/>
<glob pattern="*.obj"/>
</mime-type>
</mime-info>

View File

@ -129,9 +129,13 @@ class BuildVolume(SceneNode):
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
mb.addFace(first, previous_point, new_point, color = color)
previous_point = new_point
# Find the largest disallowed area to exclude it from the maximum scale bounds
size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
# Find the largest disallowed area to exclude it from the maximum scale bounds.
# This is a very nasty hack. This pretty much only works for UM machines. This disallowed area_size needs
# A -lot- of rework at some point in the future: TODO
if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
else:
size = 0
disallowed_area_size = max(size, disallowed_area_size)
self._disallowed_area_mesh = mb.getData()
@ -142,13 +146,16 @@ class BuildVolume(SceneNode):
skirt_size = 0.0
profile = Application.getInstance().getMachineManager().getActiveProfile()
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
skirt_size = self._getSkirtSize(profile)
# As this works better for UM machines, we only add the dissallowed_area_size for the z direction.
# This is probably wrong in all other cases. TODO!
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
scale_to_max_bounds = AxisAlignedBox(
minimum = Vector(min_w + skirt_size, min_h, min_d + skirt_size + disallowed_area_size),
maximum = Vector(max_w - skirt_size, max_h, max_d - skirt_size - disallowed_area_size)
minimum = Vector(min_w + skirt_size + 1, min_h, min_d + disallowed_area_size - skirt_size + 1),
maximum = Vector(max_w - skirt_size - 1, max_h, max_d - disallowed_area_size + skirt_size - 1)
)
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
@ -169,7 +176,7 @@ class BuildVolume(SceneNode):
if self._active_profile:
self._active_profile.settingValueChanged.disconnect(self._onSettingValueChanged)
self._active_profile = Application.getInstance().getMachineManager().getActiveProfile()
self._active_profile = Application.getInstance().getMachineManager().getWorkingProfile()
if self._active_profile:
self._active_profile.settingValueChanged.connect(self._onSettingValueChanged)
self._updateDisallowedAreas()
@ -192,6 +199,7 @@ class BuildVolume(SceneNode):
skirt_size = self._getSkirtSize(self._active_profile)
if disallowed_areas:
# Extend every area already in the disallowed_areas with the skirt size.
for area in disallowed_areas:
poly = Polygon(numpy.array(area, numpy.float32))
poly = poly.getMinkowskiHull(Polygon(numpy.array([
@ -207,6 +215,7 @@ class BuildVolume(SceneNode):
areas.append(poly)
# Add the skirt areas arround the borders of the build plate.
if skirt_size > 0:
half_machine_width = self._active_instance.getMachineSettingValue("machine_width") / 2
half_machine_depth = self._active_instance.getMachineSettingValue("machine_depth") / 2
@ -257,7 +266,8 @@ class BuildVolume(SceneNode):
if profile.getSettingValue("draft_shield_enabled"):
skirt_size += profile.getSettingValue("draft_shield_dist")
skirt_size += profile.getSettingValue("xy_offset")
if profile.getSettingValue("xy_offset"):
skirt_size += profile.getSettingValue("xy_offset")
return skirt_size

View File

@ -9,19 +9,32 @@ class ConvexHullDecorator(SceneNodeDecorator):
# In case of printing all at once this is the same as the convex hull. For one at the time this is the area without the head.
self._convex_hull_boundary = None
# In case of printing all at once this is the same as the convex hull. For one at the time this is area with full head
# In case of printing all at once this is the same as the convex hull. For one at the time this is area with intersection of mirrored head
self._convex_hull_head = None
# In case of printing all at once this is the same as the convex hull. For one at the time this is area with intersection of full head
self._convex_hull_head_full = None
self._convex_hull_node = None
self._convex_hull_job = None
self._profile = None
Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged)
Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged)
self._onActiveProfileChanged()
## Force that a new (empty) object is created upon copy.
def __deepcopy__(self, memo):
copy = ConvexHullDecorator()
return copy
def getConvexHull(self):
return self._convex_hull
def getConvexHullHeadFull(self):
if not self._convex_hull_head_full:
return self.getConvexHull()
return self._convex_hull_head_full
def getConvexHullHead(self):
if not self._convex_hull_head:
return self.getConvexHull()
@ -34,7 +47,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
def setConvexHullBoundary(self, hull):
self._convex_hull_boundary = hull
def setConvexHullHeadFull(self, hull):
self._convex_hull_head_full = hull
def setConvexHullHead(self, hull):
self._convex_hull_head = hull
@ -57,11 +73,19 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._profile:
self._profile.settingValueChanged.disconnect(self._onSettingValueChanged)
self._profile = Application.getInstance().getMachineManager().getActiveProfile()
self._profile = Application.getInstance().getMachineManager().getWorkingProfile()
if self._profile:
self._profile.settingValueChanged.connect(self._onSettingValueChanged)
def _onActiveMachineInstanceChanged(self):
if self._convex_hull_job:
self._convex_hull_job.cancel()
self.setConvexHull(None)
if self._convex_hull_node:
self._convex_hull_node.setParent(None)
self._convex_hull_node = None
def _onSettingValueChanged(self, setting):
if setting == "print_sequence":
if self._convex_hull_job:

View File

@ -39,7 +39,7 @@ class ConvexHullJob(Job):
mesh = self._node.getMeshData()
vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices()
# Don't use data below 0. TODO; We need a better check for this as this gives poor results for meshes with long edges.
vertex_data = vertex_data[vertex_data[:,1]>0]
vertex_data = vertex_data[vertex_data[:,1] >= 0]
hull = Polygon(numpy.rint(vertex_data[:, [0, 2]]).astype(int))
# First, calculate the normal convex hull around the points
@ -49,22 +49,36 @@ class ConvexHullJob(Job):
# This is done because of rounding errors.
hull = hull.getMinkowskiHull(Polygon(numpy.array([[-1, -1], [-1, 1], [1, 1], [1, -1]], numpy.float32)))
profile = Application.getInstance().getMachineManager().getActiveProfile()
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile:
if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
# Printing one at a time and it's not an object in a group
self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull))
head_hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"),numpy.float32)))
self._node.callDecoration("setConvexHullHead", head_hull)
head_and_fans = Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"), numpy.float32))
# Full head hull is used to actually check the order.
full_head_hull = hull.getMinkowskiHull(head_and_fans)
self._node.callDecoration("setConvexHullHeadFull", full_head_hull)
mirrored = copy.deepcopy(head_and_fans)
mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally.
mirrored.mirror([0, 0], [1, 0]) #Mirror vertically.
head_and_fans = head_and_fans.intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
min_head_hull = hull.getMinkowskiHull(head_and_fans)
self._node.callDecoration("setConvexHullHead", min_head_hull)
hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"),numpy.float32)))
else:
self._node.callDecoration("setConvexHullHead", None)
if self._node.getParent() is None: #Node was already deleted before job is done.
self._node.callDecoration("setConvexHullNode",None)
self._node.callDecoration("setConvexHull", None)
self._node.callDecoration("setConvexHullJob", None)
return
hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot())
self._node.callDecoration("setConvexHullNode", hull_node)
self._node.callDecoration("setConvexHull", hull)
self._node.callDecoration("setConvexHullJob", None)
if self._node.getParent().callDecoration("isGroup"):
if self._node.getParent() and self._node.getParent().callDecoration("isGroup"):
job = self._node.getParent().callDecoration("getConvexHullJob")
if job:
job.cancel()

View File

@ -5,7 +5,7 @@ from UM.Scene.SceneNode import SceneNode
from UM.Resources import Resources
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.Mesh.MeshData import MeshData
from UM.Mesh.MeshBuilder import MeshBuilder #To create a mesh to display the convex hull with.
from UM.View.GL.OpenGL import OpenGL
@ -25,6 +25,7 @@ class ConvexHullNode(SceneNode):
self._inherit_scale = False
self._color = Color(35, 35, 35, 128)
self._mesh_height = 0.1 #The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
self._node = node
self._node.transformationChanged.connect(self._onNodePositionChanged)
@ -43,22 +44,19 @@ class ConvexHullNode(SceneNode):
self._convex_hull_head_mesh = self.createHullMesh(convex_hull_head.getPoints())
def createHullMesh(self, hull_points):
mesh = MeshData()
if len(hull_points) > 3:
center = (hull_points.min(0) + hull_points.max(0)) / 2.0
mesh.addVertex(center[0], -0.1, center[1])
else:
#Input checking.
if len(hull_points) < 3:
return None
for point in hull_points:
mesh.addVertex(point[0], -0.1, point[1])
indices = []
for i in range(len(hull_points) - 1):
indices.append([0, i + 1, i + 2])
indices.append([0, mesh.getVertexCount() - 1, 1])
mesh_builder = MeshBuilder()
point_first = Vector(hull_points[0][0], self._mesh_height, hull_points[0][1])
point_previous = Vector(hull_points[1][0], self._mesh_height, hull_points[1][1])
for point in hull_points[2:]: #Add the faces in the order of a triangle fan.
point_new = Vector(point[0], self._mesh_height, point[1])
mesh_builder.addFace(point_first, point_previous, point_new, color = self._color)
point_previous = point_new #Prepare point_previous for the next triangle.
mesh.addIndices(numpy.array(indices, numpy.int32))
return mesh
return mesh_builder.getData()
def getWatchedNode(self):
return self._node

View File

@ -57,8 +57,10 @@ numpy.seterr(all="ignore")
if platform.system() == "Linux": # 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", ): # Just in case it also happens on Debian, so it can be added
from OpenGL import GL
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
from ctypes.util import find_library
ctypes.CDLL(find_library('GL'), ctypes.RTLD_GLOBAL)
try:
from cura.CuraVersion import CuraVersion
@ -76,6 +78,8 @@ class CuraApplication(QtApplication):
if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
self._open_file_queue = [] #Files to open when plug-ins are loaded.
super().__init__(name = "cura", version = CuraVersion)
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -100,10 +104,12 @@ class CuraApplication(QtApplication):
self._platform_activity = False
self._scene_boundingbox = AxisAlignedBox()
self._job_name = None
self._center_after_select = 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")
@ -114,6 +120,7 @@ class CuraApplication(QtApplication):
Preferences.getInstance().addPreference("cura/categories_expanded", "")
Preferences.getInstance().addPreference("view/center_on_select", True)
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
@ -124,6 +131,10 @@ class CuraApplication(QtApplication):
continue
self._recent_files.append(QUrl.fromLocalFile(f))
@pyqtSlot(result = QUrl)
def getDefaultPath(self):
return QUrl.fromLocalFile(os.path.expanduser("~/"))
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistery
@ -132,21 +143,21 @@ class CuraApplication(QtApplication):
if not hasattr(sys, "frozen"):
self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
self._plugin_registry.loadPlugin("ConsoleLogger")
self._plugin_registry.loadPlugin("CuraEngineBackend")
self._plugin_registry.loadPlugins()
if self.getBackend() == None:
raise RuntimeError("Could not load the backend plugin!")
self._plugins_loaded = True
def addCommandLineOptions(self, parser):
super().addCommandLineOptions(parser)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.")
def run(self):
if "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION" not in os.environ or os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] != "cpp":
Logger.log("w", "Using Python implementation of Protobuf, expect bad performance!")
self._i18n_catalog = i18nCatalog("cura");
i18nCatalog.setTagReplacements({
@ -198,13 +209,18 @@ class CuraApplication(QtApplication):
for file in self.getCommandLineOption("file", []):
self._openFile(file)
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.exec_()
# Handle Qt events
def event(self, event):
if event.type() == QEvent.FileOpen:
self._openFile(event.file())
if self._plugins_loaded:
self._openFile(event.file())
else:
self._open_file_queue.append(event.file())
return super().event(event)
@ -229,9 +245,7 @@ class CuraApplication(QtApplication):
else:
self.getController().setActiveTool("TranslateTool")
if Preferences.getInstance().getValue("view/center_on_select"):
self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
self._camera_animation.start()
self._center_after_select = True
else:
if self.getController().getActiveTool():
self._previous_active_tool = self.getController().getActiveTool().getPluginId()
@ -239,6 +253,13 @@ class CuraApplication(QtApplication):
else:
self._previous_active_tool = None
def _onToolOperationStopped(self, event):
if self._center_after_select:
self._center_after_select = False
self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
self._camera_animation.start()
requestAddPrinter = pyqtSignal()
activityChanged = pyqtSignal()
sceneBoundingBoxChanged = pyqtSignal()
@ -249,17 +270,23 @@ class CuraApplication(QtApplication):
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info", "%.1f x %.1f x %.1f mm") % (self._scene_boundingbox.width.item(), self._scene_boundingbox.depth.item(), self._scene_boundingbox.height.item())
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()}
def updatePlatformActivity(self, node = None):
count = 0
scene_boundingbox = AxisAlignedBox()
scene_boundingbox = None
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode or not node.getMeshData():
continue
count += 1
scene_boundingbox += node.getBoundingBox()
if not scene_boundingbox:
scene_boundingbox = copy.deepcopy(node.getBoundingBox())
else:
scene_boundingbox += node.getBoundingBox()
if not scene_boundingbox:
scene_boundingbox = AxisAlignedBox()
if repr(self._scene_boundingbox) != repr(scene_boundingbox):
self._scene_boundingbox = scene_boundingbox
@ -270,6 +297,7 @@ class CuraApplication(QtApplication):
@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()
@ -280,9 +308,28 @@ class CuraApplication(QtApplication):
def jobName(self):
return self._job_name
## Remove an object from the scene
# Remove all selected objects from the scene.
@pyqtSlot()
def deleteSelection(self):
if not self.getController().getToolsEnabled():
return
op = GroupedOperation()
nodes = Selection.getAllSelectedObjects()
for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node))
op.push()
pass
## Remove an object from the scene.
# Note that this only removes an object if it is selected.
@pyqtSlot("quint64")
def deleteObject(self, object_id):
if not self.getController().getToolsEnabled():
return
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
@ -342,6 +389,9 @@ class CuraApplication(QtApplication):
## Delete all mesh data on the scene.
@pyqtSlot()
def deleteAll(self):
if not self.getController().getToolsEnabled():
return
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode:
@ -462,18 +512,18 @@ class CuraApplication(QtApplication):
@pyqtSlot(str, result = "QVariant")
def getSettingValue(self, key):
if not self.getMachineManager().getActiveProfile():
if not self.getMachineManager().getWorkingProfile():
return None
return self.getMachineManager().getActiveProfile().getSettingValue(key)
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().getActiveProfile():
if not self.getMachineManager().getWorkingProfile():
return
self.getMachineManager().getActiveProfile().setSettingValue(key, value)
self.getMachineManager().getWorkingProfile().setSettingValue(key, value)
@pyqtSlot()
def mergeSelected(self):
@ -499,6 +549,7 @@ class CuraApplication(QtApplication):
group_decorator = GroupDecorator()
group_node.addDecorator(group_decorator)
group_node.setParent(self.getController().getScene().getRoot())
group_node.setSelectable(True)
center = Selection.getSelectionCenter()
group_node.setPosition(center)
group_node.setCenterPosition(center)
@ -570,9 +621,9 @@ class CuraApplication(QtApplication):
def _onFileLoaded(self, job):
node = job.getResult()
if node != None:
self.setJobName(os.path.basename(job.getFileName()))
node.setSelectable(True)
node.setName(os.path.basename(job.getFileName()))
op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
op.push()

View File

@ -1,8 +1,8 @@
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QColor, QFont
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtGui import QPixmap, QColor, QFont, QFontMetrics
from PyQt5.QtWidgets import QSplashScreen
from UM.Resources import Resources
@ -11,7 +11,10 @@ from UM.Application import Application
class CuraSplashScreen(QSplashScreen):
def __init__(self):
super().__init__()
self.setPixmap(QPixmap(Resources.getPath(Resources.Images, "cura.png")))
self._scale = round(QFontMetrics(QCoreApplication.instance().font()).ascent() / 12)
splash_image = QPixmap(Resources.getPath(Resources.Images, "cura.png"))
self.setPixmap(splash_image.scaled(splash_image.size() * self._scale))
def drawContents(self, painter):
painter.save()
@ -19,11 +22,11 @@ class CuraSplashScreen(QSplashScreen):
version = Application.getInstance().getVersion().split("-")
painter.setFont(QFont("Proxima Nova Rg", 20))
painter.drawText(0, 0, 203, 230, Qt.AlignRight | Qt.AlignBottom, version[0])
painter.setFont(QFont("Proxima Nova Rg", 20 ))
painter.drawText(0, 0, 330 * self._scale, 230 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[0])
if len(version) > 1:
painter.setFont(QFont("Proxima Nova Rg", 12))
painter.drawText(0, 0, 203, 255, Qt.AlignRight | Qt.AlignBottom, version[1])
painter.setFont(QFont("Proxima Nova Rg", 12 ))
painter.drawText(0, 0, 330 * self._scale, 255 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[1])
painter.restore()
super().drawContents(painter)

View File

@ -131,7 +131,7 @@ class Layer():
continue
if not make_mesh and not (polygon.type == Polygon.MoveCombingType or polygon.type == Polygon.MoveRetractionType):
continue
poly_color = polygon.getColor()
points = numpy.copy(polygon.data)
@ -140,26 +140,7 @@ class Layer():
if polygon.type == Polygon.MoveCombingType or polygon.type == Polygon.MoveRetractionType:
points[:,1] += 0.01
# Calculate normals for the entire polygon using numpy.
normals = numpy.copy(points)
normals[:,1] = 0.0 # We are only interested in 2D normals
# Calculate the edges between points.
# The call to numpy.roll shifts the entire array by one so that
# we end up subtracting each next point from the current, wrapping
# around. This gives us the edges from the next point to the current
# point.
normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0)
# Calculate the length of each edge using standard Pythagoras
lengths = numpy.sqrt(normals[:,0] ** 2 + normals[:,2] ** 2)
# The normal of a 2D vector is equal to its x and y coordinates swapped
# and then x inverted. This code does that.
normals[:,[0, 2]] = normals[:,[2, 0]]
normals[:,0] *= -1
# Normalize the normals.
normals[:,0] /= lengths
normals[:,2] /= lengths
normals = polygon.getNormals()
# Scale all by the line width of the polygon so we can easily offset.
normals *= (polygon.lineWidth / 2)
@ -193,22 +174,19 @@ class Polygon():
MoveRetractionType = 9
def __init__(self, mesh, type, data, line_width):
super().__init__()
self._mesh = mesh
self._type = type
self._data = data
self._line_width = line_width / 1000
self._color = self.__color_map[type]
def build(self, offset, vertices, colors, indices):
self._begin = offset
self._end = self._begin + len(self._data) - 1
color = self.getColor()
color.setValues(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
color = numpy.array([color.r, color.g, color.b, color.a], numpy.float32)
vertices[self._begin:self._end + 1, :] = self._data[:, :]
colors[self._begin:self._end + 1, :] = color
colors[self._begin:self._end + 1, :] = numpy.array([self._color.r * 0.5, self._color.g * 0.5, self._color.b * 0.5, self._color.a], numpy.float32)
for i in range(self._begin, self._end):
indices[i, 0] = i
@ -218,26 +196,7 @@ class Polygon():
indices[self._end, 1] = self._begin
def getColor(self):
if self._type == self.Inset0Type:
return Color(1.0, 0.0, 0.0, 1.0)
elif self._type == self.InsetXType:
return Color(0.0, 1.0, 0.0, 1.0)
elif self._type == self.SkinType:
return Color(1.0, 1.0, 0.0, 1.0)
elif self._type == self.SupportType:
return Color(0.0, 1.0, 1.0, 1.0)
elif self._type == self.SkirtType:
return Color(0.0, 1.0, 1.0, 1.0)
elif self._type == self.InfillType:
return Color(1.0, 0.74, 0.0, 1.0)
elif self._type == self.SupportInfillType:
return Color(0.0, 1.0, 1.0, 1.0)
elif self._type == self.MoveCombingType:
return Color(0.0, 0.0, 1.0, 1.0)
elif self._type == self.MoveRetractionType:
return Color(0.5, 0.5, 1.0, 1.0)
else:
return Color(1.0, 1.0, 1.0, 1.0)
return self._color
def vertexCount(self):
return len(self._data)
@ -257,3 +216,40 @@ class Polygon():
@property
def lineWidth(self):
return self._line_width
# Calculate normals for the entire polygon using numpy.
def getNormals(self):
normals = numpy.copy(self._data)
normals[:,1] = 0.0 # We are only interested in 2D normals
# Calculate the edges between points.
# The call to numpy.roll shifts the entire array by one so that
# we end up subtracting each next point from the current, wrapping
# around. This gives us the edges from the next point to the current
# point.
normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0)
# Calculate the length of each edge using standard Pythagoras
lengths = numpy.sqrt(normals[:,0] ** 2 + normals[:,2] ** 2)
# The normal of a 2D vector is equal to its x and y coordinates swapped
# and then x inverted. This code does that.
normals[:,[0, 2]] = normals[:,[2, 0]]
normals[:,0] *= -1
# Normalize the normals.
normals[:,0] /= lengths
normals[:,2] /= lengths
return normals
__color_map = {
NoneType: Color(1.0, 1.0, 1.0, 1.0),
Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
InsetXType: Color(0.0, 1.0, 0.0, 1.0),
SkinType: Color(1.0, 1.0, 0.0, 1.0),
SupportType: Color(0.0, 1.0, 1.0, 1.0),
SkirtType: Color(0.0, 1.0, 1.0, 1.0),
InfillType: Color(1.0, 0.74, 0.0, 1.0),
SupportInfillType: Color(0.0, 1.0, 1.0, 1.0),
MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
}

View File

@ -21,26 +21,27 @@ class OneAtATimeIterator(Iterator.Iterator):
if not type(node) is SceneNode:
continue
if node.getBoundingBox().height > Application.getInstance().getMachineManager().getActiveProfile().getSettingValue("gantry_height"):
if node.getBoundingBox().height > Application.getInstance().getMachineManager().getWorkingProfile().getSettingValue("gantry_height"):
return
if node.callDecoration("getConvexHull"):
node_list.append(node)
if len(node_list) < 2:
self._node_stack = node_list[:]
return
# Copy the list
self._original_node_list = node_list[:]
## Initialise the hit map (pre-compute all hits between all objects)
self._hit_map = [[self._checkHit(j,i) for i in node_list] for j in node_list]
self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list]
# Check if we have to files that block eachother. If this is the case, there is no solution!
for a in range(0,len(node_list)):
for b in range(0,len(node_list)):
if a != b and self._hit_map[a][b] and self._hit_map[b][a]:
return
# Sort the original list so that items that block the most other objects are at the beginning.
# This does not decrease the worst case running time, but should improve it in most cases.
sorted(node_list, key = cmp_to_key(self._calculateScore))
@ -59,44 +60,46 @@ class OneAtATimeIterator(Iterator.Iterator):
# We have no more nodes to check, so quit looking.
todo_node_list = None
self._node_stack = new_order
return
todo_node_list.append(_ObjectOrder(new_order, new_todo_list))
self._node_stack = [] #No result found!
self._node_stack = [] #No result found!
# Check if first object can be printed before the provided list (using the hit map)
def _checkHitMultiple(self, node, other_nodes):
node_index = self._original_node_list.index(node)
for other_node in other_nodes:
if self._hit_map[node_index][self._original_node_list.index(other_node)]:
other_node_index = self._original_node_list.index(other_node)
if self._hit_map[node_index][other_node_index]:
return True
return False
def _checkBlockMultiple(self, node, other_nodes):
node_index = self._original_node_list.index(node)
for other_node in other_nodes:
if self._hit_map[self._original_node_list.index(other_node)][node_index] and node_index != self._original_node_list.index(other_node):
other_node_index = self._original_node_list.index(other_node)
if self._hit_map[other_node_index][node_index] and node_index != other_node_index:
return True
return False
## Calculate score simply sums the number of other objects it 'blocks'
def _calculateScore(self, a, b):
score_a = sum(self._hit_map[self._original_node_list.index(a)])
score_b = sum(self._hit_map[self._original_node_list.index(b)])
return score_a - score_b
# Checks if A can be printed before B
def _checkHit(self, a, b):
if a == b:
return False
overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHead"))
overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull"))
if overlap:
return True
else:
return False
## Internal object used to keep track of a possible order in which to print objects.
class _ObjectOrder():
@ -107,4 +110,4 @@ class _ObjectOrder():
"""
self.order = order
self.todo = todo

View File

@ -60,12 +60,16 @@ class PlatformPhysics:
build_volume_bounding_box = copy.deepcopy(self._build_volume.getBoundingBox())
build_volume_bounding_box.setBottom(-9001) # Ignore intersections with the bottom
node._outside_buildarea = False
# Mark the node as outside the build volume if the bounding box test fails.
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
node._outside_buildarea = True
else:
node._outside_buildarea = False
# When printing one at a time too high objects are not printable.
if Application.getInstance().getMachineManager().getWorkingProfile().getSettingValue("print_sequence") == "one_at_a_time":
if node.getBoundingBox().height > Application.getInstance().getMachineManager().getWorkingProfile().getSettingValue("gantry_height"):
node._outside_buildarea = True
# Move it downwards if bottom is above platform
move_vector = Vector()

View File

@ -4,7 +4,6 @@
from PyQt5.QtCore import QObject, QDateTime, QTimer, pyqtSignal, pyqtSlot, pyqtProperty
from UM.Application import Application
from UM.Settings.MachineSettings import MachineSettings
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from UM.Qt.Duration import Duration
@ -66,6 +65,6 @@ class PrintInformation(QObject):
self.currentPrintTimeChanged.emit()
# Material amount is sent as an amount of mm^3, so calculate length from that
r = Application.getInstance().getMachineManager().getActiveProfile().getSettingValue("material_diameter") / 2
r = Application.getInstance().getMachineManager().getWorkingProfile().getSettingValue("material_diameter") / 2
self._material_amount = round((amount / (math.pi * r ** 2)) / 1000, 2)
self.materialAmountChanged.emit()

View File

@ -6,7 +6,6 @@ class ZOffsetDecorator(SceneNodeDecorator):
self._z_offset = 0
def setZOffset(self, offset):
print("setZOffset", offset)
self._z_offset = offset
def getZOffset(self):

View File

@ -12,15 +12,12 @@ def exceptHook(type, value, traceback):
sys.excepthook = exceptHook
try:
from google.protobuf.pyext import _message
except ImportError:
pass
else:
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "cpp"
if True: # To make the code style checker stop complaining
import cura.CuraApplication
# Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus
# first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread.
import Arcus
import cura.CuraApplication
if sys.platform == "win32" and hasattr(sys, "frozen"):
import os

View File

@ -24,7 +24,7 @@ import xml.etree.ElementTree as ET
class ThreeMFReader(MeshReader):
def __init__(self):
super(ThreeMFReader, self).__init__()
self._supported_extension = ".3mf"
self._supported_extensions = [".3mf"]
self._namespaces = {
"3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
@ -33,102 +33,102 @@ class ThreeMFReader(MeshReader):
def read(self, file_name):
result = None
extension = os.path.splitext(file_name)[1]
if extension.lower() == self._supported_extension:
result = SceneNode()
# The base object of 3mf is a zipped archive.
archive = zipfile.ZipFile(file_name, "r")
try:
root = ET.parse(archive.open("3D/3dmodel.model"))
# There can be multiple objects, try to load all of them.
objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
if len(objects) == 0:
Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name)
return None
result = SceneNode()
# The base object of 3mf is a zipped archive.
archive = zipfile.ZipFile(file_name, "r")
try:
root = ET.parse(archive.open("3D/3dmodel.model"))
for object in objects:
mesh = MeshData()
node = SceneNode()
vertex_list = []
#for vertex in object.mesh.vertices.vertex:
for vertex in object.findall(".//3mf:vertex", self._namespaces):
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
Job.yieldThread()
triangles = object.findall(".//3mf:triangle", self._namespaces)
mesh.reserveFaceCount(len(triangles))
#for triangle in object.mesh.triangles.triangle:
for triangle in triangles:
v1 = int(triangle.get("v1"))
v2 = int(triangle.get("v2"))
v3 = int(triangle.get("v3"))
mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2])
Job.yieldThread()
#TODO: We currently do not check for normals and simply recalculate them.
mesh.calculateNormals()
node.setMeshData(mesh)
node.setSelectable(True)
transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces)
if transformation:
transformation = transformation[0]
if transformation.get("transform"):
splitted_transformation = transformation.get("transform").split()
## Transformation is saved as:
## M00 M01 M02 0.0
## M10 M11 M12 0.0
## M20 M21 M22 0.0
## M30 M31 M32 1.0
## We switch the row & cols as that is how everyone else uses matrices!
temp_mat = Matrix()
# Rotation & Scale
temp_mat._data[0,0] = splitted_transformation[0]
temp_mat._data[1,0] = splitted_transformation[1]
temp_mat._data[2,0] = splitted_transformation[2]
temp_mat._data[0,1] = splitted_transformation[3]
temp_mat._data[1,1] = splitted_transformation[4]
temp_mat._data[2,1] = splitted_transformation[5]
temp_mat._data[0,2] = splitted_transformation[6]
temp_mat._data[1,2] = splitted_transformation[7]
temp_mat._data[2,2] = splitted_transformation[8]
# Translation
temp_mat._data[0,3] = splitted_transformation[9]
temp_mat._data[1,3] = splitted_transformation[10]
temp_mat._data[2,3] = splitted_transformation[11]
node.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3)))
temp_quaternion = Quaternion()
temp_quaternion.setByMatrix(temp_mat)
node.setOrientation(temp_quaternion)
# Magical scale extraction
scale = temp_mat.getTransposed().multiply(temp_mat)
scale_x = math.sqrt(scale.at(0,0))
scale_y = math.sqrt(scale.at(1,1))
scale_z = math.sqrt(scale.at(2,2))
node.setScale(Vector(scale_x,scale_y,scale_z))
# We use a different coordinate frame, so rotate.
#rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0))
#node.rotate(rotation)
result.addChild(node)
# There can be multiple objects, try to load all of them.
objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
if len(objects) == 0:
Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name)
return None
for object in objects:
mesh = MeshData()
node = SceneNode()
vertex_list = []
#for vertex in object.mesh.vertices.vertex:
for vertex in object.findall(".//3mf:vertex", self._namespaces):
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
Job.yieldThread()
#If there is more then one object, group them.
try:
if len(objects) > 1:
group_decorator = GroupDecorator()
result.addDecorator(group_decorator)
except:
pass
except Exception as e:
Logger.log("e" ,"exception occured in 3mf reader: %s" , e)
triangles = object.findall(".//3mf:triangle", self._namespaces)
mesh.reserveFaceCount(len(triangles))
#for triangle in object.mesh.triangles.triangle:
for triangle in triangles:
v1 = int(triangle.get("v1"))
v2 = int(triangle.get("v2"))
v3 = int(triangle.get("v3"))
mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2])
Job.yieldThread()
#TODO: We currently do not check for normals and simply recalculate them.
mesh.calculateNormals()
node.setMeshData(mesh)
node.setSelectable(True)
transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces)
if transformation:
transformation = transformation[0]
if transformation.get("transform"):
splitted_transformation = transformation.get("transform").split()
## Transformation is saved as:
## M00 M01 M02 0.0
## M10 M11 M12 0.0
## M20 M21 M22 0.0
## M30 M31 M32 1.0
## We switch the row & cols as that is how everyone else uses matrices!
temp_mat = Matrix()
# Rotation & Scale
temp_mat._data[0,0] = splitted_transformation[0]
temp_mat._data[1,0] = splitted_transformation[1]
temp_mat._data[2,0] = splitted_transformation[2]
temp_mat._data[0,1] = splitted_transformation[3]
temp_mat._data[1,1] = splitted_transformation[4]
temp_mat._data[2,1] = splitted_transformation[5]
temp_mat._data[0,2] = splitted_transformation[6]
temp_mat._data[1,2] = splitted_transformation[7]
temp_mat._data[2,2] = splitted_transformation[8]
# Translation
temp_mat._data[0,3] = splitted_transformation[9]
temp_mat._data[1,3] = splitted_transformation[10]
temp_mat._data[2,3] = splitted_transformation[11]
node.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3)))
temp_quaternion = Quaternion()
temp_quaternion.setByMatrix(temp_mat)
node.setOrientation(temp_quaternion)
# Magical scale extraction
scale = temp_mat.getTransposed().multiply(temp_mat)
scale_x = math.sqrt(scale.at(0,0))
scale_y = math.sqrt(scale.at(1,1))
scale_z = math.sqrt(scale.at(2,2))
node.setScale(Vector(scale_x,scale_y,scale_z))
# We use a different coordinate frame, so rotate.
#rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0))
#node.rotate(rotation)
result.addChild(node)
Job.yieldThread()
#If there is more then one object, group them.
try:
if len(objects) > 1:
group_decorator = GroupDecorator()
result.addDecorator(group_decorator)
except:
pass
except Exception as e:
Logger.log("e" ,"exception occured in 3mf reader: %s" , e)
return result

View File

@ -15,10 +15,12 @@ def getMetaData():
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."),
"api": 2
},
"mesh_reader": {
"extension": "3mf",
"description": catalog.i18nc("@item:inlistbox", "3MF File")
}
"mesh_reader": [
{
"extension": "3mf",
"description": catalog.i18nc("@item:inlistbox", "3MF File")
}
]
}
def register(app):

View File

@ -0,0 +1,61 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QTimer
from UM.Extension import Extension
from UM.Preferences import Preferences
from UM.Application import Application
from UM.Resources import Resources
from UM.Logger import Logger
class AutoSave(Extension):
def __init__(self):
super().__init__()
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)
machine_manager = Application.getInstance().getMachineManager()
self._profile = None
machine_manager.activeProfileChanged.connect(self._onActiveProfileChanged)
machine_manager.profileNameChanged.connect(self._triggerTimer)
machine_manager.profilesChanged.connect(self._triggerTimer)
machine_manager.machineInstanceNameChanged.connect(self._triggerTimer)
machine_manager.machineInstancesChanged.connect(self._triggerTimer)
self._onActiveProfileChanged()
Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)
self._change_timer = QTimer()
self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self._onTimeout)
self._saving = False
def _triggerTimer(self, *args):
if not self._saving:
self._change_timer.start()
def _onActiveProfileChanged(self):
if self._profile:
self._profile.settingValueChanged.disconnect(self._triggerTimer)
self._profile = Application.getInstance().getMachineManager().getWorkingProfile()
if self._profile:
self._profile.settingValueChanged.connect(self._triggerTimer)
def _onTimeout(self):
self._saving = True # To prevent the save process from triggering another autosave.
Logger.log("d", "Autosaving preferences, instances and profiles")
machine_manager = Application.getInstance().getMachineManager()
machine_manager.saveVisibility()
machine_manager.saveMachineInstances()
machine_manager.saveProfiles()
Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, Application.getInstance().getApplicationName() + ".cfg"))
self._saving = False

View File

@ -0,0 +1,21 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import AutoSave
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Auto Save"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Automatically saves Preferences, Machines and Profiles after changes."),
"api": 2
},
}
def register(app):
return { "extension": AutoSave.AutoSave() }

View File

@ -0,0 +1,104 @@
syntax = "proto3";
package cura.proto;
message ObjectList
{
repeated Object objects = 1;
repeated Setting settings = 2;
}
// typeid 1
message Slice
{
repeated ObjectList object_lists = 1;
}
message Object
{
int64 id = 1;
bytes vertices = 2; //An array of 3 floats.
bytes normals = 3; //An array of 3 floats.
bytes indices = 4; //An array of ints.
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
}
// typeid 3
message Progress
{
float amount = 1;
}
// typeid 2
message SlicedObjectList
{
repeated SlicedObject objects = 1;
}
message SlicedObject
{
int64 id = 1;
repeated Layer layers = 2;
}
message Layer {
int32 id = 1;
float height = 2;
float thickness = 3;
repeated Polygon polygons = 4;
}
message Polygon {
enum Type {
NoneType = 0;
Inset0Type = 1;
InsetXType = 2;
SkinType = 3;
SupportType = 4;
SkirtType = 5;
InfillType = 6;
SupportInfillType = 7;
MoveCombingType = 8;
MoveRetractionType = 9;
}
Type type = 1;
bytes points = 2;
float line_width = 3;
}
// typeid 4
message GCodeLayer {
int64 id = 1;
bytes data = 2;
}
// typeid 5
message ObjectPrintTime {
int64 id = 1;
float time = 2;
float material_amount = 3;
}
// typeid 6
message SettingList {
repeated Setting settings = 1;
}
message Setting {
string name = 1;
bytes value = 2;
}
// typeid 7
message GCodePrefix {
bytes data = 2;
}
// typeid 8
message SlicingFinished {
}

View File

@ -9,12 +9,13 @@ from UM.Preferences import Preferences
from UM.Math.Vector import Vector
from UM.Signal import Signal
from UM.Logger import Logger
from UM.Qt.Bindings.BackendProxy import BackendState #To determine the state of the slicing job.
from UM.Resources import Resources
from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from cura.OneAtATimeIterator import OneAtATimeIterator
from . import Cura_pb2
from . import ProcessSlicedObjectListJob
from . import ProcessGCodeJob
from . import StartSliceJob
@ -25,6 +26,8 @@ import numpy
from PyQt5.QtCore import QTimer
import Arcus
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -61,19 +64,23 @@ class CuraEngineBackend(Backend):
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self.slice)
self._message_handlers[Cura_pb2.SlicedObjectList] = self._onSlicedObjectListMessage
self._message_handlers[Cura_pb2.Progress] = self._onProgressMessage
self._message_handlers[Cura_pb2.GCodeLayer] = self._onGCodeLayerMessage
self._message_handlers[Cura_pb2.GCodePrefix] = self._onGCodePrefixMessage
self._message_handlers[Cura_pb2.ObjectPrintTime] = self._onObjectPrintTimeMessage
self._message_handlers["cura.proto.SlicedObjectList"] = self._onSlicedObjectListMessage
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage
self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage
self._message_handlers["cura.proto.ObjectPrintTime"] = self._onObjectPrintTimeMessage
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._slicing = False
self._restart = False
self._enabled = True
self._always_restart = True
self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers.
self._message = None
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
@ -107,15 +114,7 @@ class CuraEngineBackend(Backend):
return
if self._slicing:
self._slicing = False
self._restart = True
if self._process is not None:
Logger.log("d", "Killing engine process")
try:
self._process.terminate()
except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass
self._terminate()
if self._message:
self._message.hide()
@ -124,6 +123,10 @@ class CuraEngineBackend(Backend):
self.slicingCancelled.emit()
return
if self._process_layers_job:
self._process_layers_job.abort()
self._process_layers_job = None
if self._profile.hasErrorValue():
Logger.log("w", "Profile has error values. Aborting slicing")
if self._message:
@ -134,6 +137,7 @@ class CuraEngineBackend(Backend):
return #No slicing if we have error values since those are by definition illegal values.
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NOT_STARTED)
if self._message:
self._message.setProgress(-1)
#else:
@ -142,11 +146,24 @@ class CuraEngineBackend(Backend):
self._scene.gcode_list = []
self._slicing = True
self.slicingStarted.emit()
job = StartSliceJob.StartSliceJob(self._profile, self._socket)
job.start()
job.finished.connect(self._onStartSliceCompleted)
def _terminate(self):
self._slicing = False
self._restart = True
self.slicingCancelled.emit()
if self._process is not None:
Logger.log("d", "Killing engine process")
try:
self._process.terminate()
self._process = None
except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass
def _onStartSliceCompleted(self, job):
if job.getError() or job.getResult() != True:
if self._message:
@ -169,11 +186,20 @@ class CuraEngineBackend(Backend):
self._onChanged()
def _onSocketError(self, error):
super()._onSocketError(error)
self._slicing = False
self.processingProgress.emit(0)
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
Logger.log("e", "A socket error caused the connection to be reset")
def _onActiveProfileChanged(self):
if self._profile:
self._profile.settingValueChanged.disconnect(self._onSettingChanged)
self._profile = Application.getInstance().getMachineManager().getActiveProfile()
self._profile = Application.getInstance().getMachineManager().getWorkingProfile()
if self._profile:
self._profile.settingValueChanged.connect(self._onSettingChanged)
self._onChanged()
@ -183,8 +209,8 @@ class CuraEngineBackend(Backend):
def _onSlicedObjectListMessage(self, message):
if self._layer_view_active:
job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message)
job.start()
self._process_layers_job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message)
self._process_layers_job.start()
else :
self._stored_layer_data = message
@ -193,15 +219,10 @@ class CuraEngineBackend(Backend):
self._message.setProgress(round(message.amount * 100))
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.PROCESSING)
def _onGCodeLayerMessage(self, message):
self._scene.gcode_list.append(message.data.decode("utf-8", "replace"))
def _onGCodePrefixMessage(self, message):
self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace"))
def _onObjectPrintTimeMessage(self, message):
self.printDurationMessage.emit(message.time, message.material_amount)
def _onSlicingFinishedMessage(self, message):
self.backendStateChange.emit(BackendState.DONE)
self.processingProgress.emit(1.0)
self._slicing = False
@ -211,23 +232,17 @@ class CuraEngineBackend(Backend):
self._message.hide()
self._message = None
if self._always_restart:
try:
self._process.terminate()
self._createSocket()
except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass
def _onGCodeLayerMessage(self, message):
self._scene.gcode_list.append(message.data.decode("utf-8", "replace"))
def _onGCodePrefixMessage(self, message):
self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace"))
def _onObjectPrintTimeMessage(self, message):
self.printDurationMessage.emit(message.time, message.material_amount)
def _createSocket(self):
super()._createSocket()
self._socket.registerMessageType(1, Cura_pb2.Slice)
self._socket.registerMessageType(2, Cura_pb2.SlicedObjectList)
self._socket.registerMessageType(3, Cura_pb2.Progress)
self._socket.registerMessageType(4, Cura_pb2.GCodeLayer)
self._socket.registerMessageType(5, Cura_pb2.ObjectPrintTime)
self._socket.registerMessageType(6, Cura_pb2.SettingList)
self._socket.registerMessageType(7, Cura_pb2.GCodePrefix)
super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")))
## Manually triggers a reslice
def forceSlice(self):
@ -245,32 +260,31 @@ class CuraEngineBackend(Backend):
self._restart = False
def _onToolOperationStarted(self, tool):
self._terminate() # Do not continue slicing once a tool has started
self._enabled = False # Do not reslice when a tool is doing it's 'thing'
def _onToolOperationStopped(self, tool):
self._enabled = True # Tool stop, start listening for changes again.
self._onChanged()
def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView():
view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView":
self._layer_view_active = True
if self._stored_layer_data:
job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(self._stored_layer_data)
job.start()
# There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
if self._stored_layer_data and not self._slicing:
self._process_layers_job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(self._stored_layer_data)
self._process_layers_job.start()
self._stored_layer_data = None
else:
self._layer_view_active = False
def _onInstanceChanged(self):
self._slicing = False
self._restart = True
if self._process is not None:
Logger.log("d", "Killing engine process")
try:
self._process.terminate()
except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass
self._terminate()
self.slicingCancelled.emit()
def _onBackendQuit(self):
if not self._restart and self._process:
self._process = None
self._createSocket()

View File

@ -1,705 +0,0 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Cura.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='Cura.proto',
package='cura.proto',
syntax='proto3',
serialized_pb=b'\n\nCura.proto\x12\ncura.proto\"X\n\nObjectList\x12#\n\x07objects\x18\x01 \x03(\x0b\x32\x12.cura.proto.Object\x12%\n\x08settings\x18\x02 \x03(\x0b\x32\x13.cura.proto.Setting\"5\n\x05Slice\x12,\n\x0cobject_lists\x18\x01 \x03(\x0b\x32\x16.cura.proto.ObjectList\"o\n\x06Object\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x10\n\x08vertices\x18\x02 \x01(\x0c\x12\x0f\n\x07normals\x18\x03 \x01(\x0c\x12\x0f\n\x07indices\x18\x04 \x01(\x0c\x12%\n\x08settings\x18\x05 \x03(\x0b\x32\x13.cura.proto.Setting\"\x1a\n\x08Progress\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x02\"=\n\x10SlicedObjectList\x12)\n\x07objects\x18\x01 \x03(\x0b\x32\x18.cura.proto.SlicedObject\"=\n\x0cSlicedObject\x12\n\n\x02id\x18\x01 \x01(\x03\x12!\n\x06layers\x18\x02 \x03(\x0b\x32\x11.cura.proto.Layer\"]\n\x05Layer\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06height\x18\x02 \x01(\x02\x12\x11\n\tthickness\x18\x03 \x01(\x02\x12%\n\x08polygons\x18\x04 \x03(\x0b\x32\x13.cura.proto.Polygon\"\x8e\x02\n\x07Polygon\x12&\n\x04type\x18\x01 \x01(\x0e\x32\x18.cura.proto.Polygon.Type\x12\x0e\n\x06points\x18\x02 \x01(\x0c\x12\x12\n\nline_width\x18\x03 \x01(\x02\"\xb6\x01\n\x04Type\x12\x0c\n\x08NoneType\x10\x00\x12\x0e\n\nInset0Type\x10\x01\x12\x0e\n\nInsetXType\x10\x02\x12\x0c\n\x08SkinType\x10\x03\x12\x0f\n\x0bSupportType\x10\x04\x12\r\n\tSkirtType\x10\x05\x12\x0e\n\nInfillType\x10\x06\x12\x15\n\x11SupportInfillType\x10\x07\x12\x13\n\x0fMoveCombingType\x10\x08\x12\x16\n\x12MoveRetractionType\x10\t\"&\n\nGCodeLayer\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"D\n\x0fObjectPrintTime\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04time\x18\x02 \x01(\x02\x12\x17\n\x0fmaterial_amount\x18\x03 \x01(\x02\"4\n\x0bSettingList\x12%\n\x08settings\x18\x01 \x03(\x0b\x32\x13.cura.proto.Setting\"&\n\x07Setting\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\"\x1b\n\x0bGCodePrefix\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x62\x06proto3'
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_POLYGON_TYPE = _descriptor.EnumDescriptor(
name='Type',
full_name='cura.proto.Polygon.Type',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='NoneType', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Inset0Type', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='InsetXType', index=2, number=2,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SkinType', index=3, number=3,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SupportType', index=4, number=4,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SkirtType', index=5, number=5,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='InfillType', index=6, number=6,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SupportInfillType', index=7, number=7,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='MoveCombingType', index=8, number=8,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='MoveRetractionType', index=9, number=9,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=622,
serialized_end=804,
)
_sym_db.RegisterEnumDescriptor(_POLYGON_TYPE)
_OBJECTLIST = _descriptor.Descriptor(
name='ObjectList',
full_name='cura.proto.ObjectList',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='objects', full_name='cura.proto.ObjectList.objects', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='settings', full_name='cura.proto.ObjectList.settings', index=1,
number=2, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=26,
serialized_end=114,
)
_SLICE = _descriptor.Descriptor(
name='Slice',
full_name='cura.proto.Slice',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='object_lists', full_name='cura.proto.Slice.object_lists', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=116,
serialized_end=169,
)
_OBJECT = _descriptor.Descriptor(
name='Object',
full_name='cura.proto.Object',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='cura.proto.Object.id', index=0,
number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='vertices', full_name='cura.proto.Object.vertices', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='normals', full_name='cura.proto.Object.normals', index=2,
number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='indices', full_name='cura.proto.Object.indices', index=3,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='settings', full_name='cura.proto.Object.settings', index=4,
number=5, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=171,
serialized_end=282,
)
_PROGRESS = _descriptor.Descriptor(
name='Progress',
full_name='cura.proto.Progress',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='amount', full_name='cura.proto.Progress.amount', index=0,
number=1, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=284,
serialized_end=310,
)
_SLICEDOBJECTLIST = _descriptor.Descriptor(
name='SlicedObjectList',
full_name='cura.proto.SlicedObjectList',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='objects', full_name='cura.proto.SlicedObjectList.objects', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=312,
serialized_end=373,
)
_SLICEDOBJECT = _descriptor.Descriptor(
name='SlicedObject',
full_name='cura.proto.SlicedObject',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='cura.proto.SlicedObject.id', index=0,
number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='layers', full_name='cura.proto.SlicedObject.layers', index=1,
number=2, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=375,
serialized_end=436,
)
_LAYER = _descriptor.Descriptor(
name='Layer',
full_name='cura.proto.Layer',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='cura.proto.Layer.id', index=0,
number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='height', full_name='cura.proto.Layer.height', index=1,
number=2, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='thickness', full_name='cura.proto.Layer.thickness', index=2,
number=3, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='polygons', full_name='cura.proto.Layer.polygons', index=3,
number=4, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=438,
serialized_end=531,
)
_POLYGON = _descriptor.Descriptor(
name='Polygon',
full_name='cura.proto.Polygon',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='type', full_name='cura.proto.Polygon.type', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='points', full_name='cura.proto.Polygon.points', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='line_width', full_name='cura.proto.Polygon.line_width', index=2,
number=3, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_POLYGON_TYPE,
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=534,
serialized_end=804,
)
_GCODELAYER = _descriptor.Descriptor(
name='GCodeLayer',
full_name='cura.proto.GCodeLayer',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='cura.proto.GCodeLayer.id', index=0,
number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='data', full_name='cura.proto.GCodeLayer.data', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=806,
serialized_end=844,
)
_OBJECTPRINTTIME = _descriptor.Descriptor(
name='ObjectPrintTime',
full_name='cura.proto.ObjectPrintTime',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='cura.proto.ObjectPrintTime.id', index=0,
number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='time', full_name='cura.proto.ObjectPrintTime.time', index=1,
number=2, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='material_amount', full_name='cura.proto.ObjectPrintTime.material_amount', index=2,
number=3, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=846,
serialized_end=914,
)
_SETTINGLIST = _descriptor.Descriptor(
name='SettingList',
full_name='cura.proto.SettingList',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='settings', full_name='cura.proto.SettingList.settings', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=916,
serialized_end=968,
)
_SETTING = _descriptor.Descriptor(
name='Setting',
full_name='cura.proto.Setting',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='name', full_name='cura.proto.Setting.name', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='value', full_name='cura.proto.Setting.value', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=970,
serialized_end=1008,
)
_GCODEPREFIX = _descriptor.Descriptor(
name='GCodePrefix',
full_name='cura.proto.GCodePrefix',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='data', full_name='cura.proto.GCodePrefix.data', index=0,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1010,
serialized_end=1037,
)
_OBJECTLIST.fields_by_name['objects'].message_type = _OBJECT
_OBJECTLIST.fields_by_name['settings'].message_type = _SETTING
_SLICE.fields_by_name['object_lists'].message_type = _OBJECTLIST
_OBJECT.fields_by_name['settings'].message_type = _SETTING
_SLICEDOBJECTLIST.fields_by_name['objects'].message_type = _SLICEDOBJECT
_SLICEDOBJECT.fields_by_name['layers'].message_type = _LAYER
_LAYER.fields_by_name['polygons'].message_type = _POLYGON
_POLYGON.fields_by_name['type'].enum_type = _POLYGON_TYPE
_POLYGON_TYPE.containing_type = _POLYGON
_SETTINGLIST.fields_by_name['settings'].message_type = _SETTING
DESCRIPTOR.message_types_by_name['ObjectList'] = _OBJECTLIST
DESCRIPTOR.message_types_by_name['Slice'] = _SLICE
DESCRIPTOR.message_types_by_name['Object'] = _OBJECT
DESCRIPTOR.message_types_by_name['Progress'] = _PROGRESS
DESCRIPTOR.message_types_by_name['SlicedObjectList'] = _SLICEDOBJECTLIST
DESCRIPTOR.message_types_by_name['SlicedObject'] = _SLICEDOBJECT
DESCRIPTOR.message_types_by_name['Layer'] = _LAYER
DESCRIPTOR.message_types_by_name['Polygon'] = _POLYGON
DESCRIPTOR.message_types_by_name['GCodeLayer'] = _GCODELAYER
DESCRIPTOR.message_types_by_name['ObjectPrintTime'] = _OBJECTPRINTTIME
DESCRIPTOR.message_types_by_name['SettingList'] = _SETTINGLIST
DESCRIPTOR.message_types_by_name['Setting'] = _SETTING
DESCRIPTOR.message_types_by_name['GCodePrefix'] = _GCODEPREFIX
ObjectList = _reflection.GeneratedProtocolMessageType('ObjectList', (_message.Message,), dict(
DESCRIPTOR = _OBJECTLIST,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.ObjectList)
))
_sym_db.RegisterMessage(ObjectList)
Slice = _reflection.GeneratedProtocolMessageType('Slice', (_message.Message,), dict(
DESCRIPTOR = _SLICE,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Slice)
))
_sym_db.RegisterMessage(Slice)
Object = _reflection.GeneratedProtocolMessageType('Object', (_message.Message,), dict(
DESCRIPTOR = _OBJECT,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Object)
))
_sym_db.RegisterMessage(Object)
Progress = _reflection.GeneratedProtocolMessageType('Progress', (_message.Message,), dict(
DESCRIPTOR = _PROGRESS,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Progress)
))
_sym_db.RegisterMessage(Progress)
SlicedObjectList = _reflection.GeneratedProtocolMessageType('SlicedObjectList', (_message.Message,), dict(
DESCRIPTOR = _SLICEDOBJECTLIST,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.SlicedObjectList)
))
_sym_db.RegisterMessage(SlicedObjectList)
SlicedObject = _reflection.GeneratedProtocolMessageType('SlicedObject', (_message.Message,), dict(
DESCRIPTOR = _SLICEDOBJECT,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.SlicedObject)
))
_sym_db.RegisterMessage(SlicedObject)
Layer = _reflection.GeneratedProtocolMessageType('Layer', (_message.Message,), dict(
DESCRIPTOR = _LAYER,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Layer)
))
_sym_db.RegisterMessage(Layer)
Polygon = _reflection.GeneratedProtocolMessageType('Polygon', (_message.Message,), dict(
DESCRIPTOR = _POLYGON,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Polygon)
))
_sym_db.RegisterMessage(Polygon)
GCodeLayer = _reflection.GeneratedProtocolMessageType('GCodeLayer', (_message.Message,), dict(
DESCRIPTOR = _GCODELAYER,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.GCodeLayer)
))
_sym_db.RegisterMessage(GCodeLayer)
ObjectPrintTime = _reflection.GeneratedProtocolMessageType('ObjectPrintTime', (_message.Message,), dict(
DESCRIPTOR = _OBJECTPRINTTIME,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.ObjectPrintTime)
))
_sym_db.RegisterMessage(ObjectPrintTime)
SettingList = _reflection.GeneratedProtocolMessageType('SettingList', (_message.Message,), dict(
DESCRIPTOR = _SETTINGLIST,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.SettingList)
))
_sym_db.RegisterMessage(SettingList)
Setting = _reflection.GeneratedProtocolMessageType('Setting', (_message.Message,), dict(
DESCRIPTOR = _SETTING,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.Setting)
))
_sym_db.RegisterMessage(Setting)
GCodePrefix = _reflection.GeneratedProtocolMessageType('GCodePrefix', (_message.Message,), dict(
DESCRIPTOR = _GCODEPREFIX,
__module__ = 'Cura_pb2'
# @@protoc_insertion_point(class_scope:cura.proto.GCodePrefix)
))
_sym_db.RegisterMessage(GCodePrefix)
# @@protoc_insertion_point(module_scope)

View File

@ -10,6 +10,8 @@ from UM.Mesh.MeshData import MeshData
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Math.Vector import Vector
from cura import LayerData
from cura import LayerDataDecorator
@ -24,11 +26,26 @@ class ProcessSlicedObjectListJob(Job):
self._message = message
self._scene = Application.getInstance().getController().getScene()
self._progress = None
self._abort_requested = False
## Aborts the processing of layers.
#
# This abort is made on a best-effort basis, meaning that the actual
# job thread will check once in a while to see whether an abort is
# requested and then stop processing by itself. There is no guarantee
# that the abort will stop the job any time soon or even at all.
def abort(self):
self._abort_requested = True
def run(self):
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
self._progress.show()
Job.yieldThread()
if self._abort_requested:
if self._progress:
self._progress.hide()
return
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
@ -42,66 +59,84 @@ class ProcessSlicedObjectListJob(Job):
else:
object_id_map[id(node)] = node
Job.yieldThread()
if self._abort_requested:
if self._progress:
self._progress.hide()
return
settings = Application.getInstance().getMachineManager().getActiveProfile()
center = None
if not settings.getSettingValue("machine_center_is_zero"):
center = numpy.array([settings.getSettingValue("machine_width") / 2, 0.0, -settings.getSettingValue("machine_depth") / 2])
else:
center = numpy.array([0.0, 0.0, 0.0])
settings = Application.getInstance().getMachineManager().getWorkingProfile()
mesh = MeshData()
layer_data = LayerData.LayerData()
layer_count = 0
for object in self._message.objects:
layer_count += len(object.layers)
for i in range(self._message.repeatedMessageCount("objects")):
layer_count += self._message.getRepeatedMessage("objects", i).repeatedMessageCount("layers")
current_layer = 0
for object in self._message.objects:
for i in range(self._message.repeatedMessageCount("objects")):
object = self._message.getRepeatedMessage("objects", i)
try:
node = object_id_map[object.id]
except KeyError:
continue
for layer in object.layers:
for l in range(object.repeatedMessageCount("layers")):
layer = object.getRepeatedMessage("layers", l)
layer_data.addLayer(layer.id)
layer_data.setLayerHeight(layer.id, layer.height)
layer_data.setLayerThickness(layer.id, layer.thickness)
for polygon in layer.polygons:
for p in range(layer.repeatedMessageCount("polygons")):
polygon = layer.getRepeatedMessage("polygons", p)
points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
points = numpy.asarray(points, dtype=numpy.float32)
points /= 1000
points = numpy.insert(points, 1, (layer.height / 1000), axis = 1)
points[:,2] *= -1
# Create a new 3D-array, copy the 2D points over and insert the right height.
# This uses manual array creation + copy rather than numpy.insert since this is
# faster.
new_points = numpy.empty((len(points), 3), numpy.float32)
new_points[:,0] = points[:,0]
new_points[:,1] = layer.height
new_points[:,2] = -points[:,1]
points -= center
layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width)
new_points /= 1000
layer_data.addPolygon(layer.id, polygon.type, new_points, polygon.line_width)
Job.yieldThread()
Job.yieldThread()
current_layer += 1
progress = (current_layer / layer_count) * 100
# TODO: Rebuild the layer data mesh once the layer has been processed.
# This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.
if self._abort_requested:
if self._progress:
self._progress.hide()
return
if self._progress:
self._progress.setProgress(progress)
# We are done processing all the layers we got from the engine, now create a mesh out of the data
layer_data.build()
if self._abort_requested:
if self._progress:
self._progress.hide()
return
#Add layerdata decorator to scene node to indicate that the node has layerdata
decorator = LayerDataDecorator.LayerDataDecorator()
decorator.setLayerData(layer_data)
new_node.addDecorator(decorator)
new_node.setMeshData(mesh)
new_node.setParent(self._scene.getRoot())
new_node.setParent(self._scene.getRoot()) #Note: After this we can no longer abort!
if not settings.getSettingValue("machine_center_is_zero"):
new_node.setPosition(Vector(-settings.getSettingValue("machine_width") / 2, 0.0, settings.getSettingValue("machine_depth") / 2))
if self._progress:
self._progress.setProgress(100)
@ -118,6 +153,7 @@ class ProcessSlicedObjectListJob(Job):
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
if not self._progress:
self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0)
if self._progress.getProgress() != 100:
self._progress.show()
else:
if self._progress:

View File

@ -14,8 +14,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from cura.OneAtATimeIterator import OneAtATimeIterator
from . import Cura_pb2
## Formatter class that handles token expansion in start/end gcod
class GcodeStartEndFormatter(Formatter):
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
@ -63,6 +61,8 @@ class StartSliceJob(Job):
if temp_list:
object_groups.append(temp_list)
Job.yieldThread()
if len(object_groups) == 0:
Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
else:
temp_list = []
for node in DepthFirstIterator(self._scene.getRoot()):
@ -81,20 +81,23 @@ class StartSliceJob(Job):
self._sendSettings(self._profile)
slice_message = Cura_pb2.Slice()
slice_message = self._socket.createMessage("cura.proto.Slice")
for group in object_groups:
group_message = slice_message.object_lists.add()
group_message = slice_message.addRepeatedMessage("object_lists")
if group[0].getParent().callDecoration("isGroup"):
self._handlePerObjectSettings(group[0].getParent(), group_message)
for object in group:
mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
obj = group_message.objects.add()
obj = group_message.addRepeatedMessage("objects")
obj.id = id(object)
verts = numpy.array(mesh_data.getVertices())
verts[:,[1,2]] = verts[:,[2,1]]
verts[:,1] *= -1
obj.vertices = verts.tostring()
obj.vertices = verts
self._handlePerObjectSettings(object, obj)
@ -115,13 +118,13 @@ class StartSliceJob(Job):
return str(value).encode("utf-8")
def _sendSettings(self, profile):
msg = Cura_pb2.SettingList()
msg = self._socket.createMessage("cura.proto.SettingList");
settings = profile.getAllSettingValues(include_machine = True)
start_gcode = settings["machine_start_gcode"]
settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode
settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode
for key, value in settings.items():
s = msg.settings.add()
s = msg.addRepeatedMessage("settings")
s.name = key
if key == "machine_start_gcode" or key == "machine_end_gcode":
s.value = self._expandGcodeTokens(key, value, settings)
@ -134,7 +137,7 @@ class StartSliceJob(Job):
profile = node.callDecoration("getProfile")
if profile:
for key, value in profile.getAllSettingValues().items():
setting = message.settings.add()
setting = message.addRepeatedMessage("settings")
setting.name = key
setting.value = str(value).encode()
@ -143,9 +146,8 @@ class StartSliceJob(Job):
object_settings = node.callDecoration("getAllSettingValues")
if not object_settings:
return
for key, value in object_settings.items():
setting = message.settings.add()
setting = message.addRepeatedMessage("settings")
setting.name = key
setting.value = str(value).encode()

View File

@ -0,0 +1,39 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application #To get the machine manager to create the new profile in.
from UM.Settings.Profile import Profile
from UM.Settings.ProfileReader import ProfileReader
## A plugin that reads profile data from Cura profile files.
#
# It reads a profile from a .curaprofile file, and returns it as a profile
# instance.
class CuraProfileReader(ProfileReader):
## Initialises the cura profile reader.
#
# This does nothing since the only other function is basically stateless.
def __init__(self):
super().__init__()
## Reads a cura profile from a file and returns it.
#
# \param file_name The file to read the cura profile from.
# \return The cura profile that was in the file, if any. If the file could
# not be read or didn't contain a valid profile, \code None \endcode is
# returned.
def read(self, file_name):
profile = Profile(machine_manager = Application.getInstance().getMachineManager(), read_only = False) #Create an empty profile.
serialised = ""
try:
with open(file_name) as f: #Open file for reading.
serialised = f.read()
except IOError as e:
Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e))
return None
try:
profile.unserialise(serialised)
except Exception as e: #Parsing error. This is not a (valid) Cura profile then.
return None
return profile

View File

@ -0,0 +1,27 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import CuraProfileReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Cura Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing Cura profiles."),
"api": 2
},
"profile_reader": [
{
"extension": "curaprofile",
"description": catalog.i18nc("@item:inlistbox", "Cura Profile")
}
]
}
def register(app):
return { "profile_reader": CuraProfileReader.CuraProfileReader() }

View File

@ -0,0 +1,26 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2013 David Braam
# Uranium is released under the terms of the AGPLv3 or higher.
from UM.Logger import Logger
from UM.SaveFile import SaveFile
from UM.Settings.Profile import Profile
from UM.Settings.ProfileWriter import ProfileWriter
## Writes profiles to Cura's own profile format with config files.
class CuraProfileWriter(ProfileWriter):
## Writes a profile to the specified file path.
#
# \param path \type{string} The file to output to.
# \param profile \type{Profile} The profile to write to that file.
# \return \code True \endcode if the writing was successful, or \code
# False \endcode if it wasn't.
def write(self, path, profile):
serialised = profile.serialise()
try:
with SaveFile(path, "wt", -1, "utf-8") as f: #Open the specified file.
f.write(serialised)
except Exception as e:
Logger.log("e", "Failed to write profile to %s: %s", path, str(e))
return False
return True

View File

@ -0,0 +1,27 @@
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.
from . import CuraProfileWriter
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Cura Profile Writer"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for exporting Cura profiles."),
"api": 2
},
"profile_writer": [
{
"extension": "curaprofile",
"description": catalog.i18nc("@item:inlistbox", "Cura Profile")
}
]
}
def register(app):
return { "profile_writer": CuraProfileWriter.CuraProfileWriter() }

View File

@ -0,0 +1,75 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application #To get the machine manager to create the new profile in.
from UM.Settings.Profile import Profile
from UM.Settings.ProfileReader import ProfileReader
from UM.Logger import Logger
import re #Regular expressions for parsing escape characters in the settings.
## A class that reads profile data from g-code files.
#
# It reads the profile data from g-code files and stores it in a new profile.
# This class currently does not process the rest of the g-code in any way.
class GCodeProfileReader(ProfileReader):
## The file format version of the serialised g-code.
#
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 1
## Dictionary that defines how characters are escaped when embedded in
# g-code.
#
# Note that the keys of this dictionary are regex strings. The values are
# not.
escape_characters = {
re.escape("\\\\"): "\\", #The escape character.
re.escape("\\n"): "\n", #Newlines. They break off the comment.
re.escape("\\r"): "\r" #Carriage return. Windows users may need this for visualisation in their editors.
}
## Initialises the g-code reader as a profile reader.
def __init__(self):
super().__init__()
## Reads a g-code file, loading the profile from it.
#
# \param file_name The name of the file to read the profile from.
# \return The profile that was in the specified file, if any. If the
# specified file was no g-code or contained no parsable profile, \code
# None \endcode is returned.
def read(self, file_name):
if file_name.split(".")[-1] != "gcode":
return None
prefix = ";SETTING_" + str(GCodeProfileReader.version) + " "
prefix_length = len(prefix)
#Loading all settings from the file. They are all at the end, but Python has no reverse seek any more since Python3. TODO: Consider moving settings to the start?
serialised = "" #Will be filled with the serialised profile.
try:
with open(file_name) as f:
for line in f:
if line.startswith(prefix):
serialised += line[prefix_length : -1] #Remove the prefix and the newline from the line, and add it to the rest.
except IOError as e:
Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e))
return None
#Unescape the serialised profile.
pattern = re.compile("|".join(GCodeProfileReader.escape_characters.keys()))
serialised = pattern.sub(lambda m: GCodeProfileReader.escape_characters[re.escape(m.group(0))], serialised) #Perform the replacement with a regular expression.
#Apply the changes to the current profile.
profile = Profile(machine_manager = Application.getInstance().getMachineManager(), read_only = False)
try:
profile.unserialise(serialised)
profile.setType(None) #Force type to none so it's correctly added.
profile.setReadOnly(False)
profile.setDirty(True)
except Exception as e: #Not a valid g-code file.
Logger.log("e", "Unable to serialise the profile: %s", str(e))
return None
return profile

View File

@ -0,0 +1,27 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import GCodeProfileReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "GCode Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from g-code files."),
"api": 2
},
"profile_reader": [
{
"extension": "gcode",
"description": catalog.i18nc("@item:inlistbox", "G-code File")
}
]
}
def register(app):
return { "profile_reader": GCodeProfileReader.GCodeProfileReader() }

View File

@ -1,110 +0,0 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshReader import MeshReader
from UM.Application import Application
from UM.Scene.SceneNode import SceneNode
from UM.Mesh.MeshData import MeshData
from cura.LayerData import LayerData
from cura.LayerDataDecorator import LayerDataDecorator
import os
import numpy
class GCodeReader(MeshReader):
def __init__(self):
super().__init__()
self._supported_extension = ".gcode"
self._scene = Application.getInstance().getController().getScene()
def read(self, file_name):
extension = os.path.splitext(file_name)[1]
if extension.lower() == self._supported_extension:
layer_data = LayerData()
with open (file_name,"rt") as f:
layer = ""
current_path_type = ""
current_layer_nr = 0
poly_list = []
old_position = [0,0,0]
current_z = 0
for line in f:
if line.startswith(';TYPE:'):
current_path_type = line[6:].strip()
#layer_data.addPolygon(current_layer_nr,3 ,None ,5 )
elif line.startswith(';LAYER:'):
current_layer_nr = int(line[7:].strip())
layer_data.addLayer(int(line[7:].strip()))
elif line.startswith(';'):
pass # Ignore comments
else:
command_type = self.getCodeInt(line, 'G')
if command_type == 0 or command_type == 1: #Move command
x = self.getCodeFloat(line, 'X')
y = self.getCodeFloat(line, 'Y')
z = self.getCodeFloat(line, 'Z')
if z:
current_z = z
if x and y:
polygon_data = numpy.zeros((4,3)) #Square :)
polygon_data[0,:] = old_position
polygon_data[1,:] = old_position
polygon_data[2,:] = [x,current_z,y]
polygon_data[3,:] = [x,current_z,y]
old_position = [x,current_z,y]
if current_path_type == "SKIRT":
layer_data.addPolygon(current_layer_nr,5 ,polygon_data ,5 )
elif current_path_type == "WALL-INNER":
layer_data.addPolygon(current_layer_nr,3 ,polygon_data ,5 )
elif current_path_type == "WALL-OUTER":
layer_data.addPolygon(current_layer_nr,1 ,polygon_data ,5 )
else:
layer_data.addPolygon(current_layer_nr,2 ,polygon_data ,5 )
#e = self.getCodeFloat(line, 'E')
#print(x , " ", y , " ", z, " " , e)
pass
layer_data.build()
decorator = LayerDataDecorator()
decorator.setLayerData(layer_data)
new_node = SceneNode()
new_node.setMeshData(MeshData())
new_node.addDecorator(decorator)
new_node.setParent(self._scene.getRoot())
## Gets the value after the 'code' as int
# example: line = "G50" and code is "G" this function will return 50
# \param line string containing g-code.
# \param code string Letter to look for.
# \sa getCodeFloat
# \returns int
def getCodeInt(self, line, code):
n = line.find(code) + 1
if n < 1:
return None
m = line.find(' ', n)
try:
if m < 0:
return int(line[n:])
return int(line[n:m])
except:
return None
## Gets the value after the 'code' as float
# example: line = "G50" and code is "G" this function will return 50
# \param line string containing g-code.
# \param code string Letter to look for.
# \sa getCodeInt
# \returns float
def getCodeFloat(self, line, code):
n = line.find(code) + 1
if n < 1:
return None
m = line.find(' ', n)
try:
if m < 0:
return float(line[n:])
return float(line[n:m])
except:
return None

View File

@ -1,25 +0,0 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import GCodeReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "GCode Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading GCode files."),
"api": 2
},
"mesh_reader": {
"extension": "gcode",
"description": catalog.i18nc("@item:inlistbox", "Gcode File")
}
}
def register(app):
return { "mesh_reader": GCodeReader.GCodeReader() }

View File

@ -5,9 +5,29 @@ from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
import io
import re #For escaping characters in the settings.
import copy
class GCodeWriter(MeshWriter):
## The file format version of the serialised g-code.
#
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 1
## Dictionary that defines how characters are escaped when embedded in
# g-code.
#
# Note that the keys of this dictionary are regex strings. The values are
# not.
escape_characters = {
re.escape("\\"): "\\\\", #The escape character.
re.escape("\n"): "\\n", #Newlines. They break off the comment.
re.escape("\r"): "\\r" #Carriage return. Windows users may need this for visualisation in their editors.
}
def __init__(self):
super().__init__()
@ -21,6 +41,34 @@ class GCodeWriter(MeshWriter):
if gcode_list:
for gcode in gcode_list:
stream.write(gcode)
profile = self._serialiseProfile(Application.getInstance().getMachineManager().getWorkingProfile()) #Serialise the profile and put them at the end of the file.
stream.write(profile)
return True
return False
## Serialises the profile to prepare it for saving in the g-code.
#
# The profile are serialised, and special characters (including newline)
# are escaped.
#
# \param profile The profile to serialise.
# \return A serialised string of the profile.
def _serialiseProfile(self, profile):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " #The prefix to put before each line.
prefix_length = len(prefix)
#Serialise a deepcopy to remove the defaults from the profile
serialised = copy.deepcopy(profile).serialise()
#Escape characters that have a special meaning in g-code comments.
pattern = re.compile("|".join(GCodeWriter.escape_characters.keys()))
serialised = pattern.sub(lambda m: GCodeWriter.escape_characters[re.escape(m.group(0))], serialised) #Perform the replacement with a regular expression.
#Introduce line breaks so that each comment is no longer than 80 characters. Prepend each line with the prefix.
result = ""
for pos in range(0, len(serialised), 80 - prefix_length): #Lines have 80 characters, so the payload of each line is 80 - prefix.
result += prefix + serialised[pos : pos + 80 - prefix_length] + "\n"
serialised = result
return serialised

View File

@ -0,0 +1,196 @@
// Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the AGPLv3 or higher.
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import UM 1.1 as UM
UM.Dialog
{
width: 350 * Screen.devicePixelRatio;
minimumWidth: 350 * Screen.devicePixelRatio;
maximumWidth: 350 * Screen.devicePixelRatio;
height: 250 * Screen.devicePixelRatio;
minimumHeight: 250 * Screen.devicePixelRatio;
maximumHeight: 250 * Screen.devicePixelRatio;
title: catalog.i18nc("@title:window", "Convert Image...")
GridLayout
{
UM.I18nCatalog{id: catalog; name:"cura"}
anchors.fill: parent;
Layout.fillWidth: true
columnSpacing: 16
rowSpacing: 4
columns: 1
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The maximum distance of each pixel from \"Base.\"")
Row {
width: parent.width
Text {
text: catalog.i18nc("@action:label","Height (mm)")
width: 150
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: peak_height
objectName: "Peak_Height"
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: -500; top: 500;}
width: 180
onTextChanged: { manager.onPeakHeightChanged(text) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The base height from the build plate in millimeters.")
Row {
width: parent.width
Text {
text: catalog.i18nc("@action:label","Base (mm)")
width: 150
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: base_height
objectName: "Base_Height"
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 0; top: 500;}
width: 180
onTextChanged: { manager.onBaseHeightChanged(text) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The width in millimeters on the build plate.")
Row {
width: parent.width
Text {
text: catalog.i18nc("@action:label","Width (mm)")
width: 150
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: width
objectName: "Width"
focus: true
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 1; top: 500;}
width: 180
onTextChanged: { manager.onWidthChanged(text) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The depth in millimeters on the build plate")
Row {
width: parent.width
Text {
text: catalog.i18nc("@action:label","Depth (mm)")
width: 150
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: depth
objectName: "Depth"
focus: true
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 1; top: 500;}
width: 180
onTextChanged: { manager.onDepthChanged(text) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","By default, white pixels represent high points on the mesh and black pixels represent low points on the mesh. Change this option to reverse the behavior such that black pixels represent high points on the mesh and white pixels represent low points on the mesh.")
Row {
width: parent.width
//Empty label so 2 column layout works.
Text {
text: ""
width: 150
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: image_color_invert
objectName: "Image_Color_Invert"
model: [ catalog.i18nc("@item:inlistbox","Lighter is higher"), catalog.i18nc("@item:inlistbox","Darker is higher") ]
width: 180
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The amount of smoothing to apply to the image.")
Row {
width: parent.width
Text {
text: catalog.i18nc("@action:label","Smoothing")
width: 150
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: 180
height: 20
Layout.fillWidth:true
color: "transparent"
Slider {
id: smoothing
objectName: "Smoothing"
maximumValue: 100.0
stepSize: 1.0
width: 180
onValueChanged: { manager.onSmoothingChanged(value) }
}
}
}
}
}
rightButtons: [
Button
{
id:ok_button
text: catalog.i18nc("@action:button","OK");
onClicked: { manager.onOkButtonClicked() }
enabled: true
},
Button
{
id:cancel_button
text: catalog.i18nc("@action:button","Cancel");
onClicked: { manager.onCancelButtonClicked() }
enabled: true
}
]
}

View File

@ -0,0 +1,216 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os
import numpy
from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
from PyQt5.QtCore import Qt
from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshData import MeshData
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Job import Job
from UM.Logger import Logger
from .ImageReaderUI import ImageReaderUI
class ImageReader(MeshReader):
def __init__(self):
super(ImageReader, self).__init__()
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
self._ui = ImageReaderUI(self)
def preRead(self, file_name):
img = QImage(file_name)
if img.isNull():
Logger.log("e", "Image is corrupt.")
return MeshReader.PreReadResult.failed
width = img.width()
depth = img.height()
largest = max(width, depth)
width = width / largest * self._ui.default_width
depth = depth / largest * self._ui.default_depth
self._ui.setWidthAndDepth(width, depth)
self._ui.showConfigUI()
self._ui.waitForUIToClose()
if self._ui.getCancelled():
return MeshReader.PreReadResult.cancelled
return MeshReader.PreReadResult.accepted
def read(self, file_name):
size = max(self._ui.getWidth(), self._ui.getDepth())
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
mesh = None
scene_node = None
scene_node = SceneNode()
mesh = MeshData()
scene_node.setMeshData(mesh)
img = QImage(file_name)
if img.isNull():
Logger.log("e", "Image is corrupt.")
return None
width = max(img.width(), 2)
height = max(img.height(), 2)
aspect = height / width
if img.width() < 2 or img.height() < 2:
img = img.scaled(width, height, Qt.IgnoreAspectRatio)
base_height = max(base_height, 0)
peak_height = max(peak_height, -base_height)
xz_size = max(xz_size, 1)
scale_vector = Vector(xz_size, peak_height, xz_size)
if width > height:
scale_vector.setZ(scale_vector.z * aspect)
elif height > width:
scale_vector.setX(scale_vector.x / aspect)
if width > max_size or height > max_size:
scale_factor = max_size / width
if height > width:
scale_factor = max_size / height
width = int(max(round(width * scale_factor), 2))
height = int(max(round(height * scale_factor), 2))
img = img.scaled(width, height, Qt.IgnoreAspectRatio)
width_minus_one = width - 1
height_minus_one = height - 1
Job.yieldThread()
texel_width = 1.0 / (width_minus_one) * scale_vector.x
texel_height = 1.0 / (height_minus_one) * scale_vector.z
height_data = numpy.zeros((height, width), dtype=numpy.float32)
for x in range(0, width):
for y in range(0, height):
qrgb = img.pixel(x, y)
avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
height_data[y, x] = avg
Job.yieldThread()
if image_color_invert:
height_data = 1 - height_data
for i in range(0, blur_iterations):
copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")
height_data += copy[1:-1, 2:]
height_data += copy[1:-1, :-2]
height_data += copy[2:, 1:-1]
height_data += copy[:-2, 1:-1]
height_data += copy[2:, 2:]
height_data += copy[:-2, 2:]
height_data += copy[2:, :-2]
height_data += copy[:-2, :-2]
height_data /= 9
Job.yieldThread()
height_data *= scale_vector.y
height_data += base_height
heightmap_face_count = 2 * height_minus_one * width_minus_one
total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2
mesh.reserveFaceCount(total_face_count)
# initialize to texel space vertex offsets.
# 6 is for 6 vertices for each texel quad.
heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32)
heightmap_vertices = heightmap_vertices + numpy.array([[
[0, base_height, 0],
[0, base_height, texel_height],
[texel_width, base_height, texel_height],
[texel_width, base_height, texel_height],
[texel_width, base_height, 0],
[0, base_height, 0]
]], dtype = numpy.float32)
offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1]
offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height
# offsets for each texel quad
heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)
# apply height data to y values
heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)
heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)
mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices
mesh._vertex_count = heightmap_vertices.size // 3
mesh._face_count = heightmap_indices.size // 3
geo_width = width_minus_one * texel_width
geo_height = height_minus_one * texel_height
# bottom
mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)
# north and south walls
for n in range(0, width_minus_one):
x = n * texel_width
nx = (n + 1) * texel_width
hn0 = height_data[0, n]
hn1 = height_data[0, n + 1]
hs0 = height_data[height_minus_one, n]
hs1 = height_data[height_minus_one, n + 1]
mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)
mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
# west and east walls
for n in range(0, height_minus_one):
y = n * texel_height
ny = (n + 1) * texel_height
hw0 = height_data[n, 0]
hw1 = height_data[n + 1, 0]
he0 = height_data[n, width_minus_one]
he1 = height_data[n + 1, width_minus_one]
mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)
mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
mesh.calculateNormals(fast=True)
return scene_node

View File

@ -0,0 +1,155 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os
import threading
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class ImageReaderUI(QObject):
show_config_ui_trigger = pyqtSignal()
def __init__(self, image_reader):
super(ImageReaderUI, self).__init__()
self.image_reader = image_reader
self._ui_view = None
self.show_config_ui_trigger.connect(self._actualShowConfigUI)
self.default_width = 120
self.default_depth = 120
self._aspect = 1
self._width = self.default_width
self._depth = self.default_depth
self.base_height = 1
self.peak_height = 10
self.smoothing = 1
self.image_color_invert = False;
self._ui_lock = threading.Lock()
self._cancelled = False
self._disable_size_callbacks = False
def setWidthAndDepth(self, width, depth):
self._aspect = width / depth
self._width = width
self._depth = depth
def getWidth(self):
return self._width
def getDepth(self):
return self._depth
def getCancelled(self):
return self._cancelled
def waitForUIToClose(self):
self._ui_lock.acquire()
self._ui_lock.release()
def showConfigUI(self):
self._ui_lock.acquire()
self._cancelled = False
self.show_config_ui_trigger.emit()
def _actualShowConfigUI(self):
self._disable_size_callbacks = True
if self._ui_view is None:
self._createConfigUI()
self._ui_view.show()
self._ui_view.findChild(QObject, "Width").setProperty("text", str(self._width))
self._ui_view.findChild(QObject, "Depth").setProperty("text", str(self._depth))
self._disable_size_callbacks = False
self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height))
self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height))
self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing)
def _createConfigUI(self):
if self._ui_view is None:
Logger.log("d", "Creating ImageReader config UI")
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml"))
component = QQmlComponent(Application.getInstance()._engine, path)
self._ui_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._ui_context.setContextProperty("manager", self)
self._ui_view = component.create(self._ui_context)
self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);
self._disable_size_callbacks = False
@pyqtSlot()
def onOkButtonClicked(self):
self._cancelled = False
self._ui_view.close()
self._ui_lock.release()
@pyqtSlot()
def onCancelButtonClicked(self):
self._cancelled = True
self._ui_view.close()
self._ui_lock.release()
@pyqtSlot(str)
def onWidthChanged(self, value):
if self._ui_view and not self._disable_size_callbacks:
if len(value) > 0:
self._width = float(value)
else:
self._width = 0
self._depth = self._width / self._aspect
self._disable_size_callbacks = True
self._ui_view.findChild(QObject, "Depth").setProperty("text", str(self._depth))
self._disable_size_callbacks = False
@pyqtSlot(str)
def onDepthChanged(self, value):
if self._ui_view and not self._disable_size_callbacks:
if len(value) > 0:
self._depth = float(value)
else:
self._depth = 0
self._width = self._depth * self._aspect
self._disable_size_callbacks = True
self._ui_view.findChild(QObject, "Width").setProperty("text", str(self._width))
self._disable_size_callbacks = False
@pyqtSlot(str)
def onBaseHeightChanged(self, value):
if (len(value) > 0):
self.base_height = float(value)
else:
self.base_height = 0
@pyqtSlot(str)
def onPeakHeightChanged(self, value):
if (len(value) > 0):
self.peak_height = float(value)
else:
self.peak_height = 0
@pyqtSlot(float)
def onSmoothingChanged(self, value):
self.smoothing = int(value)
@pyqtSlot(int)
def onImageColorInvertChanged(self, value):
if (value == 1):
self.image_color_invert = True
else:
self.image_color_invert = False

View File

@ -0,0 +1,43 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import ImageReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "Image Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Enables ability to generate printable geometry from 2D image files."),
"api": 2
},
"mesh_reader": [
{
"extension": "jpg",
"description": i18n_catalog.i18nc("@item:inlistbox", "JPG Image")
},
{
"extension": "jpeg",
"description": i18n_catalog.i18nc("@item:inlistbox", "JPEG Image")
},
{
"extension": "png",
"description": i18n_catalog.i18nc("@item:inlistbox", "PNG Image")
},
{
"extension": "bmp",
"description": i18n_catalog.i18nc("@item:inlistbox", "BMP Image")
},
{
"extension": "gif",
"description": i18n_catalog.i18nc("@item:inlistbox", "GIF Image")
}
]
}
def register(app):
return { "mesh_reader": ImageReader.ImageReader() }

View File

@ -10,16 +10,23 @@ from UM.Signal import Signal
from UM.Scene.Selection import Selection
from UM.Math.Color import Color
from UM.Mesh.MeshData import MeshData
from UM.Job import Job
from UM.Message import Message
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.ConvexHullNode import ConvexHullNode
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication
from . import LayerViewProxy
import time
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## View used to display g-code paths.
class LayerView(View):
def __init__(self):
@ -29,27 +36,45 @@ class LayerView(View):
self._num_layers = 0
self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
self._proxy = LayerViewProxy.LayerViewProxy()
self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._max_layers = 10
self._current_layer_num = 10
self._current_layer_mesh = None
self._current_layer_jumps = None
self._top_layers_job = None
self._activity = False
self._solid_layers = 5
self._top_layer_timer = QTimer()
self._top_layer_timer.setInterval(50)
self._top_layer_timer.setSingleShot(True)
self._top_layer_timer.timeout.connect(self._startUpdateTopLayers)
self._busy = False
def getActivity(self):
return self._activity
def getCurrentLayer(self):
return self._current_layer_num
def _onSceneChanged(self, node):
self.calculateMaxLayers()
def getMaxLayers(self):
return self._max_layers
busyChanged = Signal()
def isBusy(self):
return self._busy
def setBusy(self, busy):
if busy != self._busy:
self._busy = busy
self.busyChanged.emit()
def resetLayerData(self):
self._current_layer_mesh = None
self._current_layer_jumps = None
@ -59,7 +84,7 @@ class LayerView(View):
renderer = self.getRenderer()
if not self._selection_shader:
self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))
for node in DepthFirstIterator(scene.getRoot()):
@ -89,51 +114,11 @@ class LayerView(View):
# This uses glDrawRangeElements internally to only draw a certain range of lines.
renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))
# We currently recreate the current "solid" layers every time a
if not self._current_layer_mesh:
self._current_layer_mesh = MeshData()
for i in range(self._solid_layers):
layer = self._current_layer_num - i
if layer < 0:
continue
try:
layer_mesh = layer_data.getLayer(layer).createMesh()
if not layer_mesh or layer_mesh.getVertices() is None:
continue
except:
continue
if self._current_layer_mesh: #Threading thing; Switching between views can cause the current layer mesh to be deleted.
self._current_layer_mesh.addVertices(layer_mesh.getVertices())
# Scale layer color by a brightness factor based on the current layer number
# This will result in a range of 0.5 - 1.0 to multiply colors by.
brightness = (2.0 - (i / self._solid_layers)) / 2.0
if self._current_layer_mesh:
self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness)
if self._current_layer_mesh:
renderer.queueNode(node, mesh = self._current_layer_mesh)
if not self._current_layer_jumps:
self._current_layer_jumps = MeshData()
for i in range(1):
layer = self._current_layer_num - i
if layer < 0:
continue
try:
layer_mesh = layer_data.getLayer(layer).createJumps()
if not layer_mesh or layer_mesh.getVertices() is None:
continue
except:
continue
self._current_layer_jumps.addVertices(layer_mesh.getVertices())
# Scale layer color by a brightness factor based on the current layer number
# This will result in a range of 0.5 - 1.0 to multiply colors by.
brightness = (2.0 - (i / self._solid_layers)) / 2.0
self._current_layer_jumps.addColors(layer_mesh.getColors() * brightness)
renderer.queueNode(node, mesh = self._current_layer_jumps)
if self._current_layer_jumps:
renderer.queueNode(node, mesh = self._current_layer_jumps)
def setLayer(self, value):
if self._current_layer_num != value:
@ -145,6 +130,9 @@ class LayerView(View):
self._current_layer_mesh = None
self._current_layer_jumps = None
self._top_layer_timer.start()
self.currentLayerNumChanged.emit()
currentLayerNumChanged = Signal()
@ -167,27 +155,32 @@ class LayerView(View):
if new_max_layers > 0 and new_max_layers != self._old_max_layers:
self._max_layers = new_max_layers
self.maxLayersChanged.emit()
self._current_layer_num = self._max_layers
# This makes sure we update the current layer
self.setLayer(int(self._max_layers))
self.currentLayerNumChanged.emit()
# The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
# if it's the largest value. If we don't do this, we can have a slider block outside of the
# slider.
if new_max_layers > self._current_layer_num:
self.maxLayersChanged.emit()
self.setLayer(int(self._max_layers))
else:
self.setLayer(int(self._max_layers))
self.maxLayersChanged.emit()
self._top_layer_timer.start()
maxLayersChanged = Signal()
currentLayerNumChanged = Signal()
## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
# as this caused some issues.
def getProxy(self, engine, script_engine):
return self._proxy
def endRendering(self):
pass
def event(self, event):
modifiers = QtWidgets.QApplication.keyboardModifiers()
ctrl_is_active = modifiers == QtCore.Qt.ControlModifier
modifiers = QApplication.keyboardModifiers()
ctrl_is_active = modifiers == Qt.ControlModifier
if event.type == Event.KeyPressEvent and ctrl_is_active:
if event.key == KeyEvent.UpKey:
self.setLayer(self._current_layer_num + 1)
@ -195,3 +188,86 @@ class LayerView(View):
if event.key == KeyEvent.DownKey:
self.setLayer(self._current_layer_num - 1)
return True
def _startUpdateTopLayers(self):
if self._top_layers_job:
self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
self._top_layers_job.cancel()
self.setBusy(True)
self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
self._top_layers_job.start()
def _updateCurrentLayerMesh(self, job):
self.setBusy(False)
if not job.getResult():
return
self._current_layer_mesh = job.getResult().get("layers")
self._current_layer_jumps = job.getResult().get("jumps")
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
self._top_layers_job = None
class _CreateTopLayersJob(Job):
def __init__(self, scene, layer_number, solid_layers):
super().__init__()
self._scene = scene
self._layer_number = layer_number
self._solid_layers = solid_layers
self._cancel = False
def run(self):
layer_data = None
for node in DepthFirstIterator(self._scene.getRoot()):
layer_data = node.callDecoration("getLayerData")
if layer_data:
break
if self._cancel or not layer_data:
return
layer_mesh = MeshData()
for i in range(self._solid_layers):
layer_number = self._layer_number - i
if layer_number < 0:
continue
try:
layer = layer_data.getLayer(layer_number).createMesh()
except Exception as e:
print(e)
return
if not layer or layer.getVertices() is None:
continue
layer_mesh.addVertices(layer.getVertices())
# Scale layer color by a brightness factor based on the current layer number
# This will result in a range of 0.5 - 1.0 to multiply colors by.
brightness = (2.0 - (i / self._solid_layers)) / 2.0
layer_mesh.addColors(layer.getColors() * brightness)
if self._cancel:
return
Job.yieldThread()
if self._cancel:
return
Job.yieldThread()
jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
if not jump_mesh or jump_mesh.getVertices() is None:
jump_mesh = None
self.setResult({ "layers": layer_mesh, "jumps": jump_mesh })
def cancel(self):
self._cancel = True
super().cancel()

View File

@ -8,37 +8,98 @@ import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
Item
Item
{
width: UM.Theme.sizes.button.width
height: UM.Theme.sizes.slider_layerview_size.height
width: UM.Theme.getSize("button").width
height: UM.Theme.getSize("slider_layerview_size").height
Slider
Slider
{
id: slider
width: UM.Theme.sizes.slider_layerview_size.width
height: UM.Theme.sizes.slider_layerview_size.height
width: UM.Theme.getSize("slider_layerview_size").width
height: UM.Theme.getSize("slider_layerview_size").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.sizes.slider_layerview_margin.width/2
anchors.leftMargin: UM.Theme.getSize("slider_layerview_margin").width/2
orientation: Qt.Vertical
minimumValue: 0;
maximumValue: UM.LayerView.numLayers;
stepSize: 1
property real pixelsPerStep: ((height - UM.Theme.getSize("slider_handle").height) / (maximumValue - minimumValue)) * stepSize;
value: UM.LayerView.currentLayer
onValueChanged: UM.LayerView.setCurrentLayer(value)
style: UM.Theme.styles.layerViewSlider
style: UM.Theme.styles.slider;
Rectangle
{
x: parent.width + UM.Theme.getSize("slider_layerview_background").width / 2;
y: parent.height - (parent.value * parent.pixelsPerStep) - UM.Theme.getSize("slider_handle").height * 1.25;
height: UM.Theme.getSize("slider_handle").height + UM.Theme.getSize("default_margin").height
width: valueLabel.width + UM.Theme.getSize("default_margin").width
Behavior on height { NumberAnimation { duration: 50; } }
border.width: UM.Theme.getSize("default_lining").width;
border.color: UM.Theme.getColor("slider_groove_border");
visible: UM.LayerView.getLayerActivity && Printer.getPlatformActivity ? true : false
TextField
{
id: valueLabel
property string maxValue: slider.maximumValue + 1
text: slider.value + 1
horizontalAlignment: TextInput.AlignRight;
onEditingFinished:
{
if(valueLabel.text != '')
{
slider.value = valueLabel.text - 1
}
}
validator: IntValidator { bottom: 1; top: slider.maximumValue + 1; }
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2;
anchors.verticalCenter: parent.verticalCenter;
width: UM.Theme.getSize("line").width * maxValue.length;
style: TextFieldStyle
{
textColor: UM.Theme.getColor("setting_control_text");
font: UM.Theme.getFont("default");
background: Item { }
}
}
BusyIndicator
{
id: busyIndicator;
anchors.left: parent.right;
anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2;
anchors.verticalCenter: parent.verticalCenter;
width: UM.Theme.getSize("slider_handle").height;
height: width;
running: UM.LayerView.busy;
visible: UM.LayerView.busy;
}
}
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
z: slider.z - 1
width: UM.Theme.sizes.slider_layerview_background.width
height: slider.height + UM.Theme.sizes.default_margin.height * 2
color: UM.Theme.colors.tool_panel_background;
border.width: UM.Theme.sizes.default_lining.width
border.color: UM.Theme.colors.lining
width: UM.Theme.getSize("slider_layerview_background").width
height: slider.height + UM.Theme.getSize("default_margin").height * 2
color: UM.Theme.getColor("tool_panel_background");
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
MouseArea {
id: sliderMouseArea

View File

@ -33,6 +33,15 @@ class LayerViewProxy(QObject):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getCurrentLayer()
busyChanged = pyqtSignal()
@pyqtProperty(bool, notify = busyChanged)
def busy(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.isBusy()
return False
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
@ -49,9 +58,13 @@ class LayerViewProxy(QObject):
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()
def _onBusyChanged(self):
self.busyChanged.emit()
def _onActiveViewChanged(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
active_view.busyChanged.connect(self._onBusyChanged)

View File

@ -18,7 +18,8 @@ def getMetaData():
},
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layers"),
"view_panel": "LayerView.qml"
"view_panel": "LayerView.qml",
"weight": 2
}
}

View File

@ -0,0 +1,157 @@
{
"source_version": "15.04",
"target_version": 1,
"translation": {
"machine_nozzle_size": "nozzle_size",
"line_width": "wall_thickness if (spiralize == \"True\" or simple_mode == \"True\") else (nozzle_size if (float(wall_thickness) < 0.01) else (wall_thickness if (float(wall_thickness) < float(nozzle_size)) else (nozzle_size if ((int(float(wall_thickness) / (float(nozzle_size) - 0.0001))) == 0) else ((float(wall_thickness) / ((int(float(wall_thickness) / (float(nozzle_size) - 0.0001))) + 1)) if ((float(wall_thickness) / (int(float(wall_thickness) / (float(nozzle_size) - 0.0001)))) > float(nozzle_size) * 1.5) else ((float(wall_thickness) / (int(float(wall_thickness) / (float(nozzle_size) - 0.0001)))))))))",
"layer_height": "layer_height",
"layer_height_0": "bottom_thickness",
"wall_thickness": "wall_thickness",
"top_bottom_thickness": "solid_layer_thickness",
"top_thickness": "0 if (solid_top == \"False\") else solid_layer_thickness",
"bottom_thickness": "0 if (solid_bottom == \"False\") else solid_layer_thickness",
"infill_sparse_density": "fill_density",
"infill_overlap": "fill_overlap",
"infill_before_walls": "False if (perimeter_before_infill == \"True\") else True",
"material_print_temperature": "print_temperature",
"material_bed_temperature": "print_bed_temperature",
"material_diameter": "filament_diameter",
"material_flow": "filament_flow",
"retraction_enable": "retraction_enable",
"retraction_amount": "retraction_amount",
"retraction_speed": "retraction_speed",
"retraction_min_travel": "retraction_min_travel",
"retraction_hop": "retraction_hop",
"speed_print": "print_speed",
"speed_infill": "infill_speed if (float(infill_speed) != 0) else print_speed",
"speed_wall_0": "inset0_speed if (float(inset0_speed) != 0) else print_speed",
"speed_wall_x": "insetx_speed if (float(insetx_speed) != 0) else print_speed",
"speed_topbottom": "solidarea_speed if (float(solidarea_speed) != 0) else print_speed",
"speed_travel": "travel_speed if (float(travel_speed) != 0) else travel_speed",
"speed_layer_0": "bottom_layer_speed",
"retraction_combing": "True if (retraction_combing == \"All\" or retraction_combing == \"No Skin\") else False",
"cool_fan_enabled": "fan_enabled",
"cool_fan_speed_min": "fan_speed",
"cool_fan_speed_max": "fan_speed_max",
"cool_fan_full_at_height": "fan_full_height",
"cool_min_layer_time": "cool_min_layer_time",
"cool_min_speed": "cool_min_feedrate",
"cool_lift_head": "cool_head_lift",
"support_enable": "False if (support == \"None\") else True",
"support_type": "\"buildplate\" if (support == \"Touching buildplate\") else \"everywhere\"",
"support_angle": "support_angle",
"support_xy_distance": "support_xy_distance",
"support_z_distance": "support_z_distance",
"support_pattern": "support_type.lower()",
"support_infill_rate": "support_fill_rate",
"adhesion_type": "\"skirt\" if (platform_adhesion == \"None\") else platform_adhesion.lower()",
"skirt_line_count": "skirt_line_count",
"skirt_gap": "skirt_gap",
"skirt_minimal_length": "skirt_minimal_length",
"brim_line_count": "brim_line_count",
"raft_margin": "raft_margin",
"raft_airgap": "raft_airgap_all",
"raft_surface_layers": "raft_surface_layers",
"raft_surface_thickness": "raft_surface_thickness",
"raft_surface_line_width": "raft_surface_linewidth",
"raft_surface_line_spacing": "raft_line_spacing",
"raft_interface_thickness": "raft_interface_thickness",
"raft_interface_line_width": "raft_interface_linewidth",
"raft_interface_line_spacing": "raft_line_spacing",
"raft_base_thickness": "raft_base_thickness",
"raft_base_line_width": "raft_base_linewidth",
"raft_base_line_spacing": "raft_line_spacing",
"meshfix_union_all": "fix_horrible_union_all_type_a",
"meshfix_union_all_remove_holes": "fix_horrible_union_all_type_b",
"meshfix_extensive_stitching": "fix_horrible_extensive_stitching",
"meshfix_keep_open_polygons": "fix_horrible_use_open_bits",
"magic_mesh_surface_mode": "simple_mode",
"magic_spiralize": "spiralize",
"prime_tower_enable": "wipe_tower",
"prime_tower_size": "math.sqrt(float(wipe_tower_volume) / float(layer_height))",
"ooze_shield_enabled": "ooze_shield"
},
"defaults": {
"bottom_layer_speed": "20",
"bottom_thickness": "0.3",
"brim_line_count": "20",
"cool_head_lift": "False",
"cool_min_feedrate": "10",
"cool_min_layer_time": "5",
"fan_enabled": "True",
"fan_full_height": "0.5",
"fan_speed": "100",
"fan_speed_max": "100",
"filament_diameter": "2.85",
"filament_diameter2": "0",
"filament_diameter3": "0",
"filament_diameter4": "0",
"filament_diameter5": "0",
"filament_flow": "100.0",
"fill_density": "20",
"fill_overlap": "15",
"fix_horrible_extensive_stitching": "False",
"fix_horrible_union_all_type_a": "True",
"fix_horrible_union_all_type_b": "False",
"fix_horrible_use_open_bits": "False",
"infill_speed": "0.0",
"inset0_speed": "0.0",
"insetx_speed": "0.0",
"layer_height": "0.1",
"layer0_width_factor": "100",
"nozzle_size": "0.4",
"object_sink": "0.0",
"ooze_shield": "False",
"overlap_dual": "0.15",
"perimeter_before_infill": "False",
"platform_adhesion": "None",
"print_bed_temperature": "70",
"print_speed": "50",
"print_temperature": "210",
"print_temperature2": "0",
"print_temperature3": "0",
"print_temperature4": "0",
"print_temperature5": "0",
"raft_airgap": "0.22",
"raft_airgap_all": "0.0",
"raft_base_linewidth": "1.0",
"raft_base_thickness": "0.3",
"raft_interface_linewidth": "0.4",
"raft_interface_thickness": "0.27",
"raft_line_spacing": "3.0",
"raft_margin": "5.0",
"raft_surface_layers": "2",
"raft_surface_linewidth": "0.4",
"raft_surface_thickness": "0.27",
"retraction_amount": "4.5",
"retraction_combing": "All",
"retraction_dual_amount": "16.5",
"retraction_enable": "True",
"retraction_hop": "0.0",
"retraction_min_travel": "1.5",
"retraction_minimal_extrusion": "0.02",
"retraction_speed": "40.0",
"simple_mode": "False",
"skirt_gap": "3.0",
"skirt_line_count": "1",
"skirt_minimal_length": "150.0",
"solid_bottom": "True",
"solid_layer_thickness": "0.6",
"solid_top": "True",
"solidarea_speed": "0.0",
"spiralize": "False",
"support": "None",
"support_angle": "60",
"support_dual_extrusion": "Both",
"support_fill_rate": "15",
"support_type": "Lines",
"support_xy_distance": "0.7",
"support_z_distance": "0.15",
"travel_speed": "150.0",
"wall_thickness": "0.8",
"wipe_tower": "False",
"wipe_tower_volume": "15"
}
}

View File

@ -0,0 +1,128 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #For reading the legacy profile INI files.
import json #For reading the Dictionary of Doom.
import math #For mathematical operations included in the Dictionary of Doom.
import os.path #For concatenating the path to the plugin and the relative path to the Dictionary of Doom.
from UM.Application import Application #To get the machine manager to create the new profile in.
from UM.Logger import Logger #Logging errors.
from UM.PluginRegistry import PluginRegistry #For getting the path to this plugin's directory.
from UM.Settings.Profile import Profile
from UM.Settings.ProfileReader import ProfileReader
## A plugin that reads profile data from legacy Cura versions.
#
# It reads a profile from an .ini file, and performs some translations on it.
# Not all translations are correct, mind you, but it is a best effort.
class LegacyProfileReader(ProfileReader):
## Initialises the legacy profile reader.
#
# This does nothing since the only other function is basically stateless.
def __init__(self):
super().__init__()
## Prepares the default values of all legacy settings.
#
# These are loaded from the Dictionary of Doom.
#
# \param json The JSON file to load the default setting values from. This
# should not be a URL but a pre-loaded JSON handle.
# \return A dictionary of the default values of the legacy Cura version.
def prepareDefaults(self, json):
defaults = {}
for key in json["defaults"]: #We have to copy over all defaults from the JSON handle to a normal dict.
defaults[key] = json["defaults"][key]
return defaults
## Prepares the local variables that can be used in evaluation of computing
# new setting values from the old ones.
#
# This fills a dictionary with all settings from the legacy Cura version
# and their values, so that they can be used in evaluating the new setting
# values as Python code.
#
# \param config_parser The ConfigParser that finds the settings in the
# legacy profile.
# \param config_section The section in the profile where the settings
# should be found.
# \param defaults The default values for all settings in the legacy Cura.
# \return A set of local variables, one for each setting in the legacy
# profile.
def prepareLocals(self, config_parser, config_section, defaults):
locals = defaults.copy() #Don't edit the original!
for option in config_parser.options(config_section):
locals[option] = config_parser.get(config_section, option)
return locals
## Reads a legacy Cura profile from a file and returns it.
#
# \param file_name The file to read the legacy Cura profile from.
# \return The legacy Cura profile that was in the file, if any. If the
# file could not be read or didn't contain a valid profile, \code None
# \endcode is returned.
def read(self, file_name):
if file_name.split(".")[-1] != "ini":
return None
Logger.log("i", "Importing legacy profile from file " + file_name + ".")
profile = Profile(machine_manager = Application.getInstance().getMachineManager(), read_only = False) #Create an empty profile.
parser = configparser.ConfigParser(interpolation = None)
try:
with open(file_name) as f:
parser.readfp(f) #Parse the INI file.
except Exception as e:
Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e))
return None
#Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile".
#Since importing multiple machine profiles is out of scope, just import the first section we find.
section = ""
for found_section in parser.sections():
if found_section.startswith("profile"):
section = found_section
break
if not section: #No section starting with "profile" was found. Probably not a proper INI file.
return None
try:
with open(os.path.join(PluginRegistry.getInstance().getPluginPath("LegacyProfileReader"), "DictionaryOfDoom.json"), "r", -1, "utf-8") as f:
dict_of_doom = json.load(f) #Parse the Dictionary of Doom.
except IOError as e:
Logger.log("e", "Could not open DictionaryOfDoom.json for reading: %s", str(e))
return None
except Exception as e:
Logger.log("e", "Could not parse DictionaryOfDoom.json: %s", str(e))
return None
defaults = self.prepareDefaults(dict_of_doom)
legacy_settings = self.prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile.
#Check the target version in the Dictionary of Doom with this application version.
if "target_version" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no target version. Is it the correct JSON file?")
return None
if Profile.ProfileVersion != dict_of_doom["target_version"]:
Logger.log("e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the profile version (version %s)!", dict_of_doom["target_version"], str(Profile.ProfileVersion))
return None
if "translation" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
return None
for new_setting in dict_of_doom["translation"]: #Evaluate all new settings that would get a value from the translations.
old_setting_expression = dict_of_doom["translation"][new_setting]
compiled = compile(old_setting_expression, new_setting, "eval")
try:
new_value = eval(compiled, {"math": math}, legacy_settings) #Pass the legacy settings as local variables to allow access to in the evaluation.
value_using_defaults = eval(compiled, {"math": math}, defaults) #Evaluate again using only the default values to try to see if they are default.
except Exception as e: #Probably some setting name that was missing or something else that went wrong in the ini file.
Logger.log("w", "Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile.")
continue
if new_value != value_using_defaults and profile.getSettingValue(new_setting) != new_value: #Not equal to the default in the new Cura OR the default in the legacy Cura.
profile.setSettingValue(new_setting, new_value) #Store the setting in the profile!
if len(profile.getChangedSettings()) == 0:
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
profile.setDirty(True)
return profile

View File

@ -0,0 +1,27 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import LegacyProfileReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Legacy Cura Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from legacy Cura versions."),
"api": 2
},
"profile_reader": [
{
"extension": "ini",
"description": catalog.i18nc("@item:inlistbox", "Cura 15.04 profiles")
}
]
}
def register(app):
return { "profile_reader": LegacyProfileReader.LegacyProfileReader() }

View File

@ -24,16 +24,11 @@ class PerObjectSettingsModel(ListModel):
super().__init__(parent)
self._scene = Application.getInstance().getController().getScene()
self._root = self._scene.getRoot()
self._root.transformationChanged.connect(self._updatePositions)
self._root.childrenChanged.connect(self._updateNodes)
self._updateNodes(None)
self.addRoleName(self.IdRole,"id")
self.addRoleName(self.XRole,"x")
self.addRoleName(self.YRole,"y")
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.ProfileRole, "profile")
self.addRoleName(self.SettingsRole, "settings")
self._updateModel()
@pyqtSlot("quint64", str)
def setObjectProfile(self, object_id, profile_name):
@ -72,27 +67,11 @@ class PerObjectSettingsModel(ListModel):
if len(node.callDecoration("getAllSettings")) == 0:
node.removeDecorator(SettingOverrideDecorator)
def _updatePositions(self, source):
camera = Application.getInstance().getController().getScene().getActiveCamera()
for node in BreadthFirstIterator(self._root):
if type(node) is not SceneNode or not node.getMeshData():
continue
projected_position = camera.project(node.getWorldPosition())
index = self.find("id", id(node))
self.setProperty(index, "x", float(projected_position[0]))
self.setProperty(index, "y", float(projected_position[1]))
def _updateNodes(self, source):
def _updateModel(self):
self.clear()
camera = Application.getInstance().getController().getScene().getActiveCamera()
for node in BreadthFirstIterator(self._root):
if type(node) is not SceneNode or not node.getMeshData() or not node.isSelectable():
if type(node) is not SceneNode or not node.isSelectable():
continue
projected_position = camera.project(node.getWorldPosition())
node_profile = node.callDecoration("getProfile")
if not node_profile:
node_profile = "global"
@ -101,8 +80,6 @@ class PerObjectSettingsModel(ListModel):
self.appendItem({
"id": id(node),
"x": float(projected_position[0]),
"y": float(projected_position[1]),
"material": "",
"profile": node_profile,
"settings": SettingOverrideModel.SettingOverrideModel(node)

View File

@ -10,8 +10,9 @@ import UM 1.1 as UM
Item {
id: base;
property int currentIndex: UM.ActiveTool.properties.SelectedIndex;
property string printSequence: UM.ActiveTool.properties.PrintSequence;
property int currentIndex: UM.ActiveTool.properties.getValue("SelectedIndex")
UM.I18nCatalog { id: catalog; name: "cura"; }
width: childrenRect.width;
height: childrenRect.height;
@ -21,51 +22,21 @@ Item {
anchors.top: parent.top;
anchors.left: parent.left;
spacing: UM.Theme.sizes.default_margin.height;
Label {
width: UM.Theme.sizes.setting.width;
wrapMode: Text.Wrap;
text: catalog.i18nc("@label", "Per Object Settings behavior may be unexpected when 'Print sequence' is set to 'All at Once'.")
color: UM.Theme.colors.text;
visible: base.printSequence == "all_at_once"
}
UM.SettingItem {
id: profileSelection
width: UM.Theme.sizes.setting.width;
height: UM.Theme.sizes.setting.height;
name: catalog.i18nc("@label", "Object profile")
type: "enum"
indent: false
style: UM.Theme.styles.setting_item;
options: UM.ProfilesModel { addUseGlobal: true }
value: UM.ActiveTool.properties.Model.getItem(base.currentIndex).profile
onItemValueChanged: {
var item = UM.ActiveTool.properties.Model.getItem(base.currentIndex);
UM.ActiveTool.properties.Model.setObjectProfile(item.id, value)
}
}
spacing: UM.Theme.getSize("default_margin").height;
Column {
id: customisedSettings
spacing: UM.Theme.sizes.default_lining.height;
width: UM.Theme.sizes.setting.width + UM.Theme.sizes.setting.height/2;
spacing: UM.Theme.getSize("default_lining").height;
width: UM.Theme.getSize("setting").width + UM.Theme.getSize("setting").height/2;
Repeater {
id: settings;
model: UM.ActiveTool.properties.Model.getItem(base.currentIndex).settings
model: UM.ActiveTool.properties.getValue("Model").getItem(base.currentIndex).settings
UM.SettingItem {
width: UM.Theme.sizes.setting.width;
height: UM.Theme.sizes.setting.height;
width: UM.Theme.getSize("setting").width;
height: UM.Theme.getSize("setting").height;
name: model.label;
type: model.type;
@ -73,6 +44,7 @@ Item {
description: model.description;
unit: model.unit;
valid: model.valid;
visible: !model.global_only
options: model.options
indent: false
@ -86,10 +58,10 @@ Item {
{
anchors.left: parent.right;
width: UM.Theme.sizes.setting.height;
height: UM.Theme.sizes.setting.height;
width: UM.Theme.getSize("setting").height;
height: UM.Theme.getSize("setting").height;
onClicked: UM.ActiveTool.properties.Model.removeSettingOverride(UM.ActiveTool.properties.Model.getItem(base.currentIndex).id, model.key)
onClicked: UM.ActiveTool.properties.getValue("Model").removeSettingOverride(UM.ActiveTool.properties.getValue("Model").getItem(base.currentIndex).id, model.key)
style: ButtonStyle
{
@ -104,8 +76,8 @@ Item {
height: parent.height/2
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.colors.setting_control_revert
source: UM.Theme.icons.cross1
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
source: UM.Theme.getIcon("cross1")
}
}
}
@ -117,8 +89,7 @@ Item {
Button
{
id: customise_settings_button;
anchors.right: profileSelection.right;
height: UM.Theme.sizes.setting.height;
height: UM.Theme.getSize("setting").height;
visible: parseInt(UM.Preferences.getValue("cura/active_mode")) == 1
text: catalog.i18nc("@action:button", "Add Setting");
@ -129,16 +100,16 @@ Item {
{
width: control.width;
height: control.height;
border.width: UM.Theme.sizes.default_lining.width;
border.color: control.pressed ? UM.Theme.colors.action_button_active_border :
control.hovered ? UM.Theme.colors.action_button_hovered_border : UM.Theme.colors.action_button_border
color: control.pressed ? UM.Theme.colors.action_button_active :
control.hovered ? UM.Theme.colors.action_button_hovered : UM.Theme.colors.action_button
border.width: UM.Theme.getSize("default_lining").width;
border.color: control.pressed ? UM.Theme.getColor("action_button_active_border") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
color: control.pressed ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
}
label: Label
{
text: control.text;
color: UM.Theme.colors.setting_control_text;
color: UM.Theme.getColor("setting_control_text");
anchors.centerIn: parent
}
}
@ -157,7 +128,6 @@ Item {
}
}
UM.I18nCatalog { id: catalog; name: "uranium"; }
UM.Dialog {
id: settingPickDialog
@ -188,7 +158,7 @@ Item {
}
Column {
width: view.width - UM.Theme.sizes.default_margin.width * 2;
width: view.width - UM.Theme.getSize("default_margin").width * 2;
height: childrenRect.height;
Repeater {
@ -201,6 +171,7 @@ Item {
width: parent.width;
height: childrenRect.height;
visible: model.visible && settingsColumn.childrenHeight != 0 //If all children are hidden, the height is 0, and then the category header must also be hidden.
ToolButton {
id: categoryHeader;
@ -218,11 +189,11 @@ Item {
}
label: Row
{
spacing: UM.Theme.sizes.default_margin.width;
spacing: UM.Theme.getSize("default_margin").width;
Image
{
anchors.verticalCenter: parent.verticalCenter;
source: control.checked ? UM.Theme.icons.arrow_right : UM.Theme.icons.arrow_bottom;
source: control.checked ? UM.Theme.getIcon("arrow_right") : UM.Theme.getIcon("arrow_bottom");
}
Label
{
@ -236,8 +207,6 @@ Item {
property variant settingsModel: model.settings;
visible: model.visible;
Column {
id: settingsColumn;
@ -268,13 +237,15 @@ Item {
delegate: ToolButton {
id: button;
x: model.depth * UM.Theme.sizes.default_margin.width;
x: model.depth * UM.Theme.getSize("default_margin").width;
text: model.name;
tooltip: model.description;
visible: !model.global_only
height: model.global_only ? 0 : undefined
onClicked: {
var object_id = UM.ActiveTool.properties.Model.getItem(base.currentIndex).id;
UM.ActiveTool.properties.Model.addSettingOverride(object_id, model.key);
var object_id = UM.ActiveTool.properties.getValue("Model").getItem(base.currentIndex).id;
UM.ActiveTool.properties.getValue("Model").addSettingOverride(object_id, model.key);
settingPickDialog.visible = false;
}

View File

@ -4,26 +4,44 @@
from UM.Tool import Tool
from UM.Scene.Selection import Selection
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from . import PerObjectSettingsModel
class PerObjectSettingsTool(Tool):
def __init__(self):
super().__init__()
self._model = None
self.setExposedProperties("Model", "SelectedIndex", "PrintSequence")
self.setExposedProperties("Model", "SelectedIndex")
Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged)
self._onPreferenceChanged("cura/active_mode")
def event(self, event):
return False
def getModel(self):
return PerObjectSettingsModel.PerObjectSettingsModel()
if not self._model:
self._model = PerObjectSettingsModel.PerObjectSettingsModel()
#For some reason, casting this model to itself causes the model to properly be cast to a QVariant, even though it ultimately inherits from QVariant.
#Yeah, we have no idea either...
return PerObjectSettingsModel.PerObjectSettingsModel(self._model)
def getSelectedIndex(self):
selected_object_id = id(Selection.getSelectedObject(0))
try:
selected_object = Selection.getSelectedObject(0)
if selected_object.getParent().callDecoration("isGroup"):
selected_object = selected_object.getParent()
except:
selected_object = None
selected_object_id = id(selected_object)
index = self.getModel().find("id", selected_object_id)
return index
def getPrintSequence(self):
settings = Application.getInstance().getMachineManager().getActiveProfile()
return settings.getSettingValue("print_sequence")
def _onPreferenceChanged(self, preference):
if preference == "cura/active_mode":
enabled = Preferences.getInstance().getValue(preference)==1
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, enabled)

View File

@ -18,6 +18,7 @@ class SettingOverrideModel(ListModel):
OptionsRole = Qt.UserRole + 8
WarningDescriptionRole = Qt.UserRole + 9
ErrorDescriptionRole = Qt.UserRole + 10
GlobalOnlyRole = Qt.UserRole + 11
def __init__(self, node, parent = None):
super().__init__(parent)
@ -28,6 +29,10 @@ class SettingOverrideModel(ListModel):
self._node.decoratorsChanged.connect(self._onDecoratorsChanged)
self._onDecoratorsChanged(None)
self._activeProfile = Application.getInstance().getMachineManager().getWorkingProfile() #To be able to get notified when a setting changes.
self._activeProfile.settingValueChanged.connect(self._onProfileSettingValueChanged)
Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onProfileChanged)
self.addRoleName(self.KeyRole, "key")
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.DescriptionRole, "description")
@ -38,6 +43,7 @@ class SettingOverrideModel(ListModel):
self.addRoleName(self.OptionsRole, "options")
self.addRoleName(self.WarningDescriptionRole, "warning_description")
self.addRoleName(self.ErrorDescriptionRole, "error_description")
self.addRoleName(self.GlobalOnlyRole, "global_only")
@pyqtSlot(str, "QVariant")
def setSettingValue(self, key, value):
@ -68,6 +74,35 @@ class SettingOverrideModel(ListModel):
model.appendItem({"value": str(value), "name": str(name)})
return model
## Updates the active profile in this model if the active profile is
# changed.
#
# This links the settingValueChanged of the new profile to this model's
# _onSettingValueChanged function, so that it properly listens to those
# events again.
def _onProfileChanged(self):
if self._activeProfile: #Unlink from the old profile.
self._activeProfile.settingValueChanged.disconnect(self._onProfileSettingValueChanged)
old_profile = self._activeProfile
self._activeProfile = Application.getInstance().getMachineManager().getWorkingProfile()
self._activeProfile.settingValueChanged.connect(self._onProfileSettingValueChanged) #Re-link to the new profile.
for setting_name in old_profile.getChangedSettings().keys(): #Update all changed settings in the old and new profiles.
self._onProfileSettingValueChanged(setting_name)
for setting_name in self._activeProfile.getChangedSettings().keys():
self._onProfileSettingValueChanged(setting_name)
## Updates the global_only property of a setting once a setting value
# changes.
#
# This method should only get called on settings that are dependent on the
# changed setting.
#
# \param setting_name The setting that needs to be updated.
def _onProfileSettingValueChanged(self, setting_name):
index = self.find("key", setting_name)
if index != -1:
self.setProperty(index, "global_only", Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getSetting(setting_name).getGlobalOnly())
def _onSettingsChanged(self):
self.clear()
@ -84,7 +119,8 @@ class SettingOverrideModel(ListModel):
"valid": setting.validate(value),
"options": self._createOptionsModel(setting.getOptions()),
"warning_description": setting.getWarningDescription(),
"error_description": setting.getErrorDescription()
"error_description": setting.getErrorDescription(),
"global_only": setting.getGlobalOnly()
})
items.sort(key = lambda i: i["key"])
@ -98,3 +134,4 @@ class SettingOverrideModel(ListModel):
if index != -1:
self.setProperty(index, "value", str(value))
self.setProperty(index, "valid", setting.validate(value))
self.setProperty(index, "global_only", setting.getGlobalOnly())

View File

@ -4,7 +4,7 @@
from . import PerObjectSettingsTool
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {

View File

@ -8,6 +8,7 @@ from UM.Mesh.MeshWriter import MeshWriter
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice import OutputDeviceError
from UM.Preferences import Preferences
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -24,14 +25,19 @@ class RemovableDriveOutputDevice(OutputDevice):
self._writing = False
def requestWrite(self, node, file_name = None):
def requestWrite(self, node, file_name = None, filter_by_machine = False):
if self._writing:
raise OutputDeviceError.DeviceBusyError()
gcode_writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType("text/x-gcode")
if not gcode_writer:
Logger.log("e", "Could not find GCode writer, not writing to removable drive %s", self.getName())
file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite() #Formats supported by this application.
if filter_by_machine:
machine_file_formats = Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getFileFormats()
file_formats = list(filter(lambda file_format: file_format["mime_type"] in machine_file_formats, file_formats)) #Take the intersection between file_formats and machine_file_formats.
if len(file_formats) == 0:
Logger.log("e", "There are no file formats available to write with!")
raise OutputDeviceError.WriteRequestFailedError()
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"]) #Just take the first file format available.
extension = file_formats[0]["extension"]
if file_name == None:
for n in BreadthFirstIterator(node):
@ -44,12 +50,14 @@ class RemovableDriveOutputDevice(OutputDevice):
Logger.log("e", "Could not determine a proper file name when trying to write to %s, aborting", self.getName())
raise OutputDeviceError.WriteRequestFailedError()
file_name = os.path.join(self.getId(), os.path.splitext(file_name)[0] + ".gcode")
if extension: #Not empty string.
extension = "." + extension
file_name = os.path.join(self.getId(), os.path.splitext(file_name)[0] + extension)
try:
Logger.log("d", "Writing to %s", file_name)
stream = open(file_name, "wt")
job = WriteMeshJob(gcode_writer, stream, node, MeshWriter.OutputMode.TextMode)
job = WriteMeshJob(writer, stream, node, MeshWriter.OutputMode.TextMode)
job.setFileName(file_name)
job.progress.connect(self._onProgress)
job.finished.connect(self._onFinished)

View File

@ -44,7 +44,7 @@ class SliceInfo(Extension):
def _onWriteStarted(self, output_device):
if not Preferences.getInstance().getValue("info/send_slice_info"):
return # Do nothing, user does not want to send data
settings = Application.getInstance().getMachineManager().getActiveProfile()
settings = Application.getInstance().getMachineManager().getWorkingProfile()
# Load all machine definitions and put them in machine_settings dict
#setting_file_name = Application.getInstance().getActiveMachineInstance().getMachineSettings()._json_file

View File

@ -33,14 +33,17 @@ class SolidView(View):
if not self._disabled_shader:
self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
self._disabled_shader.setUniformValue("u_diffuseColor", [0.68, 0.68, 0.68, 1.0])
self._disabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
if Application.getInstance().getMachineManager().getActiveProfile():
profile = Application.getInstance().getMachineManager().getActiveProfile()
if Application.getInstance().getMachineManager().getWorkingProfile():
profile = Application.getInstance().getMachineManager().getWorkingProfile()
if profile.getSettingValue("support_enable") or not Preferences.getInstance().getValue("view/show_overhang"):
if Preferences.getInstance().getValue("view/show_overhang"):
angle = profile.getSettingValue("support_angle")
if angle != None:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))

View File

@ -16,7 +16,8 @@ def getMetaData():
"api": 2
},
"view": {
"name": i18n_catalog.i18nc("@item:inmenu", "Solid")
"name": i18n_catalog.i18nc("@item:inmenu", "Solid"),
"weight": 0
}
}

View File

@ -21,7 +21,7 @@ UM.Dialog
anchors.fill: parent;
Row
{
spacing: UM.Theme.sizes.default_margin.width;
spacing: UM.Theme.getSize("default_margin").width;
Text
{
//: USB Printing dialog label, %1 is head temperature

View File

@ -52,12 +52,13 @@ UM.Dialog
wrapMode: Text.Wrap;
}
ProgressBar
ProgressBar
{
id: prog;
id: prog
value: manager.progress
minimumValue: 0;
maximumValue: 100;
minimumValue: 0
maximumValue: 100
indeterminate: manager.progress < 100
anchors
{
left: parent.left;
@ -65,7 +66,7 @@ UM.Dialog
}
}
SystemPalette
{
id: palette;

View File

@ -98,7 +98,7 @@ class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
@pyqtSlot()
def updateAllFirmware(self):
if not self._printer_connections:
Message("Cannot update firmware, there were no connected printers found.").show()
Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show()
return
self.spawnFirmwareInterface("")
@ -106,6 +106,7 @@ class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
try:
self._printer_connections[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
except FileNotFoundError:
self._printer_connections[printer_connection].setProgress(100, 100)
Logger.log("w", "No firmware found for printer %s", printer_connection)
continue
@ -132,31 +133,38 @@ class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
return USBPrinterManager._instance
def _getDefaultFirmwareName(self):
machine_type = Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getId()
firmware_name = ""
machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance()
machine_type = machine_instance.getMachineDefinition().getId()
baudrate = 250000
if sys.platform.startswith("linux"):
baudrate = 115200
if machine_type == "ultimaker_original":
firmware_name = "MarlinUltimaker"
if machine_instance.getMachineSettingValue("machine_heated_bed"): #Has heated bed upgrade kit?
firmware_name += "-HBK"
firmware_name += "-%d" % (baudrate)
return firmware_name + ".hex"
elif machine_type == "ultimaker_original_plus":
firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate)
elif machine_type == "Witbox":
return firmware_name + ".hex"
elif machine_type == "bq_witbox":
return "MarlinWitbox.hex"
elif machine_type == "ultimaker2go":
elif machine_type == "ultimaker2_go":
return "MarlinUltimaker2go.hex"
elif machine_type == "ultimaker2extended":
elif machine_type == "ultimaker2_extended":
return "MarlinUltimaker2extended.hex"
elif machine_type == "ultimaker2":
return "MarlinUltimaker2.hex"
elif machine_type == "ultimaker2plus":
return "MarlinUltimaker2plus.hex"
elif machine_type == "ultimaker2_extended_plus":
return "MarlinUltimaker2extended-plus.hex"
else:
Logger.log("e", "I don't know of any firmware for machine %s.", machine_type)
raise FileNotFoundError()
##TODO: Add check for multiple extruders
if firmware_name != "":
firmware_name += ".hex"
return firmware_name
def _addRemovePorts(self, serial_ports):
# First, find and add all new or changed keys
for serial_port in list(serial_ports):

View File

@ -16,7 +16,8 @@ def getMetaData():
"api": 2
},
"view": {
"name": catalog.i18nc("@item:inlistbox", "X-Ray")
"name": catalog.i18nc("@item:inlistbox", "X-Ray"),
"weight": 1
}
}

File diff suppressed because it is too large Load Diff

1757
resources/i18n/de/cura.po Executable file → Normal file

File diff suppressed because it is too large Load Diff

924
resources/i18n/de/fdmprinter.json.po Executable file → Normal file

File diff suppressed because it is too large Load Diff

1320
resources/i18n/en/cura.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1320
resources/i18n/es/cura.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2015-09-12 20:10+0000\n"
"POT-Creation-Date: 2016-01-18 11:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
@ -11,6 +11,21 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: fdmprinter.json
msgctxt "machine label"
msgid "Machine"
msgstr ""
#: fdmprinter.json
msgctxt "machine_nozzle_size label"
msgid "Nozzle Diameter"
msgstr ""
#: fdmprinter.json
msgctxt "machine_nozzle_size description"
msgid "The inner diameter of the nozzle."
msgstr ""
#: fdmprinter.json
msgctxt "resolution label"
msgid "Quality"
@ -184,8 +199,8 @@ msgstr ""
#: fdmprinter.json
msgctxt "wall_line_count description"
msgid ""
"Number of shell lines. This these lines are called perimeter lines in other "
"tools and impact the strength and structural integrity of your print."
"Number of shell lines. These lines are called perimeter lines in other tools "
"and impact the strength and structural integrity of your print."
msgstr ""
#: fdmprinter.json
@ -209,9 +224,9 @@ msgstr ""
#: fdmprinter.json
msgctxt "top_bottom_thickness description"
msgid ""
"This controls the thickness of the bottom and top layers, the amount of "
"solid layers put down is calculated by the layer thickness and this value. "
"Having this value a multiple of the layer thickness makes sense. And keep it "
"This controls the thickness of the bottom and top layers. The number of "
"solid layers put down is calculated from the layer thickness and this value. "
"Having this value a multiple of the layer thickness makes sense. Keep it "
"near your wall thickness to make an evenly strong part."
msgstr ""
@ -225,8 +240,8 @@ msgctxt "top_thickness description"
msgid ""
"This controls the thickness of the top layers. The number of solid layers "
"printed is calculated from the layer thickness and this value. Having this "
"value be a multiple of the layer thickness makes sense. And keep it nearto "
"your wall thickness to make an evenly strong part."
"value be a multiple of the layer thickness makes sense. Keep it near your "
"wall thickness to make an evenly strong part."
msgstr ""
#: fdmprinter.json
@ -236,7 +251,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "top_layers description"
msgid "This controls the amount of top layers."
msgid "This controls the number of top layers."
msgstr ""
#: fdmprinter.json
@ -351,7 +366,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "top_bottom_pattern description"
msgid ""
"Pattern of the top/bottom solid fill. This normally is done with lines to "
"Pattern of the top/bottom solid fill. This is normally done with lines to "
"get the best possible finish, but in some cases a concentric fill gives a "
"nicer end result."
msgstr ""
@ -373,13 +388,13 @@ msgstr ""
#: fdmprinter.json
msgctxt "skin_no_small_gaps_heuristic label"
msgid "Ingore small Z gaps"
msgid "Ignore small Z gaps"
msgstr ""
#: fdmprinter.json
msgctxt "skin_no_small_gaps_heuristic description"
msgid ""
"When the model has small vertical gaps about 5% extra computation time can "
"When the model has small vertical gaps, about 5% extra computation time can "
"be spent on generating top and bottom skin in these narrow spaces. In such a "
"case set this setting to false."
msgstr ""
@ -394,19 +409,19 @@ msgctxt "skin_alternate_rotation description"
msgid ""
"Alternate between diagonal skin fill and horizontal + vertical skin fill. "
"Although the diagonal directions can print quicker, this option can improve "
"on the printing quality by reducing the pillowing effect."
"the printing quality by reducing the pillowing effect."
msgstr ""
#: fdmprinter.json
msgctxt "skin_outline_count label"
msgid "Skin Perimeter Line Count"
msgid "Extra Skin Wall Count"
msgstr ""
#: fdmprinter.json
msgctxt "skin_outline_count description"
msgid ""
"Number of lines around skin regions. Using one or two skin perimeter lines "
"can greatly improve on roofs which would start in the middle of infill cells."
"can greatly improve roofs which would start in the middle of infill cells."
msgstr ""
#: fdmprinter.json
@ -417,7 +432,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "xy_offset description"
msgid ""
"Amount of offset applied all polygons in each layer. Positive values can "
"Amount of offset applied to all polygons in each layer. Positive values can "
"compensate for too big holes; negative values can compensate for too small "
"holes."
msgstr ""
@ -430,11 +445,11 @@ msgstr ""
#: fdmprinter.json
msgctxt "z_seam_type description"
msgid ""
"Starting point of each part in a layer. When parts in consecutive layers "
"Starting point of each path in a layer. When paths in consecutive layers "
"start at the same point a vertical seam may show on the print. When aligning "
"these at the back, the seam is easiest to remove. When placed randomly the "
"inaccuracies at the part start will be less noticable. When taking the "
"shortest path the print will be more quick."
"inaccuracies at the paths' start will be less noticeable. When taking the "
"shortest path the print will be quicker."
msgstr ""
#: fdmprinter.json
@ -466,8 +481,8 @@ msgstr ""
msgctxt "infill_sparse_density description"
msgid ""
"This controls how densely filled the insides of your print will be. For a "
"solid part use 100%, for an hollow part use 0%. A value around 20% is "
"usually enough. This won't affect the outside of the print and only adjusts "
"solid part use 100%, for a hollow part use 0%. A value around 20% is usually "
"enough. This setting won't affect the outside of the print and only adjusts "
"how strong the part becomes."
msgstr ""
@ -489,7 +504,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "infill_pattern description"
msgid ""
"Cura defaults to switching between grid and line infill. But with this "
"Cura defaults to switching between grid and line infill, but with this "
"setting visible you can control this yourself. The line infill swaps "
"direction on alternate layers of infill, while the grid prints the full "
"cross-hatching on each layer of infill."
@ -505,6 +520,11 @@ msgctxt "infill_pattern option lines"
msgid "Lines"
msgstr ""
#: fdmprinter.json
msgctxt "infill_pattern option triangles"
msgid "Triangles"
msgstr ""
#: fdmprinter.json
msgctxt "infill_pattern option concentric"
msgid "Concentric"
@ -536,7 +556,7 @@ msgstr ""
msgctxt "infill_wipe_dist description"
msgid ""
"Distance of a travel move inserted after every infill line, to make the "
"infill stick to the walls better. This option is imilar to infill overlap, "
"infill stick to the walls better. This option is similar to infill overlap, "
"but without extrusion and only on one end of the infill line."
msgstr ""
@ -553,16 +573,6 @@ msgid ""
"save printing time."
msgstr ""
#: fdmprinter.json
msgctxt "infill_sparse_combine label"
msgid "Infill Layers"
msgstr ""
#: fdmprinter.json
msgctxt "infill_sparse_combine description"
msgid "Amount of layers that are combined together to form sparse infill."
msgstr ""
#: fdmprinter.json
msgctxt "infill_before_walls label"
msgid "Infill Before Walls"
@ -582,6 +592,18 @@ msgctxt "material label"
msgid "Material"
msgstr ""
#: fdmprinter.json
msgctxt "material_flow_dependent_temperature label"
msgid "Auto Temperature"
msgstr ""
#: fdmprinter.json
msgctxt "material_flow_dependent_temperature description"
msgid ""
"Change the temperature for each layer automatically with the average flow "
"speed of that layer."
msgstr ""
#: fdmprinter.json
msgctxt "material_print_temperature label"
msgid "Printing Temperature"
@ -595,6 +617,42 @@ msgid ""
"For ABS a value of 230C or higher is required."
msgstr ""
#: fdmprinter.json
msgctxt "material_flow_temp_graph label"
msgid "Flow Temperature Graph"
msgstr ""
#: fdmprinter.json
msgctxt "material_flow_temp_graph description"
msgid ""
"Data linking material flow (in mm3 per second) to temperature (degrees "
"Celsius)."
msgstr ""
#: fdmprinter.json
msgctxt "material_standby_temperature label"
msgid "Standby Temperature"
msgstr ""
#: fdmprinter.json
msgctxt "material_standby_temperature description"
msgid ""
"The temperature of the nozzle when another nozzle is currently used for "
"printing."
msgstr ""
#: fdmprinter.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
msgstr ""
#: fdmprinter.json
msgctxt "material_extrusion_cool_down_speed description"
msgid ""
"The extra speed by which the nozzle cools while extruding. The same value is "
"used to signify the heat up speed lost when heating up while extruding."
msgstr ""
#: fdmprinter.json
msgctxt "material_bed_temperature label"
msgid "Bed Temperature"
@ -617,7 +675,7 @@ msgctxt "material_diameter description"
msgid ""
"The diameter of your filament needs to be measured as accurately as "
"possible.\n"
"If you cannot measure this value you will have to calibrate it, a higher "
"If you cannot measure this value you will have to calibrate it; a higher "
"number means less extrusion, a smaller number generates more extrusion."
msgstr ""
@ -654,7 +712,7 @@ msgstr ""
msgctxt "retraction_amount description"
msgid ""
"The amount of retraction: Set at 0 for no retraction at all. A value of "
"4.5mm seems to generate good results for 3mm filament in Bowden-tube fed "
"4.5mm seems to generate good results for 3mm filament in bowden tube fed "
"printers."
msgstr ""
@ -700,8 +758,8 @@ msgstr ""
#: fdmprinter.json
msgctxt "retraction_extra_prime_amount description"
msgid ""
"The amount of material extruded after unretracting. During a retracted "
"travel material might get lost and so we need to compensate for this."
"The amount of material extruded after a retraction. During a travel move, "
"some material might get lost and so we need to compensate for this."
msgstr ""
#: fdmprinter.json
@ -713,33 +771,33 @@ msgstr ""
msgctxt "retraction_min_travel description"
msgid ""
"The minimum distance of travel needed for a retraction to happen at all. "
"This helps ensure you do not get a lot of retractions in a small area."
"This helps to get fewer retractions in a small area."
msgstr ""
#: fdmprinter.json
msgctxt "retraction_count_max label"
msgid "Maximal Retraction Count"
msgid "Maximum Retraction Count"
msgstr ""
#: fdmprinter.json
msgctxt "retraction_count_max description"
msgid ""
"This settings limits the number of retractions occuring within the Minimal "
"This setting limits the number of retractions occurring within the Minimum "
"Extrusion Distance Window. Further retractions within this window will be "
"ignored. This avoids retracting repeatedly on the same piece of filament as "
"ignored. This avoids retracting repeatedly on the same piece of filament, as "
"that can flatten the filament and cause grinding issues."
msgstr ""
#: fdmprinter.json
msgctxt "retraction_extrusion_window label"
msgid "Minimal Extrusion Distance Window"
msgid "Minimum Extrusion Distance Window"
msgstr ""
#: fdmprinter.json
msgctxt "retraction_extrusion_window description"
msgid ""
"The window in which the Maximal Retraction Count is enforced. This window "
"should be approximately the size of the Retraction distance, so that "
"The window in which the Maximum Retraction Count is enforced. This value "
"should be approximately the same as the Retraction distance, so that "
"effectively the number of times a retraction passes the same patch of "
"material is limited."
msgstr ""
@ -753,7 +811,7 @@ msgstr ""
msgctxt "retraction_hop description"
msgid ""
"Whenever a retraction is done, the head is lifted by this amount to travel "
"over the print. A value of 0.075 works well. This feature has a lot of "
"over the print. A value of 0.075 works well. This feature has a large "
"positive effect on delta towers."
msgstr ""
@ -796,7 +854,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "speed_wall description"
msgid ""
"The speed at which shell is printed. Printing the outer shell at a lower "
"The speed at which the shell is printed. Printing the outer shell at a lower "
"speed improves the final skin quality."
msgstr ""
@ -808,7 +866,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "speed_wall_0 description"
msgid ""
"The speed at which outer shell is printed. Printing the outer shell at a "
"The speed at which the outer shell is printed. Printing the outer shell at a "
"lower speed improves the final skin quality. However, having a large "
"difference between the inner shell speed and the outer shell speed will "
"effect quality in a negative way."
@ -822,8 +880,8 @@ msgstr ""
#: fdmprinter.json
msgctxt "speed_wall_x description"
msgid ""
"The speed at which all inner shells are printed. Printing the inner shell "
"fasster than the outer shell will reduce printing time. It is good to set "
"The speed at which all inner shells are printed. Printing the inner shell "
"faster than the outer shell will reduce printing time. It works well to set "
"this in between the outer shell speed and the infill speed."
msgstr ""
@ -849,8 +907,9 @@ msgstr ""
msgctxt "speed_support description"
msgid ""
"The speed at which exterior support is printed. Printing exterior supports "
"at higher speeds can greatly improve printing time. And the surface quality "
"of exterior support is usually not important, so higher speeds can be used."
"at higher speeds can greatly improve printing time. The surface quality of "
"exterior support is usually not important anyway, so higher speeds can be "
"used."
msgstr ""
#: fdmprinter.json
@ -862,7 +921,7 @@ msgstr ""
msgctxt "speed_support_lines description"
msgid ""
"The speed at which the walls of exterior support are printed. Printing the "
"walls at higher speeds can improve on the overall duration. "
"walls at higher speeds can improve the overall duration."
msgstr ""
#: fdmprinter.json
@ -874,7 +933,7 @@ msgstr ""
msgctxt "speed_support_roof description"
msgid ""
"The speed at which the roofs of exterior support are printed. Printing the "
"support roof at lower speeds can improve on overhang quality. "
"support roof at lower speeds can improve overhang quality."
msgstr ""
#: fdmprinter.json
@ -886,7 +945,7 @@ msgstr ""
msgctxt "speed_travel description"
msgid ""
"The speed at which travel moves are done. A well-built Ultimaker can reach "
"speeds of 250mm/s. But some machines might have misaligned layers then."
"speeds of 250mm/s, but some machines might have misaligned layers then."
msgstr ""
#: fdmprinter.json
@ -898,7 +957,7 @@ msgstr ""
msgctxt "speed_layer_0 description"
msgid ""
"The print speed for the bottom layer: You want to print the first layer "
"slower so it sticks to the printer bed better."
"slower so it sticks better to the printer bed."
msgstr ""
#: fdmprinter.json
@ -910,19 +969,19 @@ msgstr ""
msgctxt "skirt_speed description"
msgid ""
"The speed at which the skirt and brim are printed. Normally this is done at "
"the initial layer speed. But sometimes you want to print the skirt at a "
"different speed."
"the initial layer speed, but sometimes you might want to print the skirt at "
"a different speed."
msgstr ""
#: fdmprinter.json
msgctxt "speed_slowdown_layers label"
msgid "Amount of Slower Layers"
msgid "Number of Slower Layers"
msgstr ""
#: fdmprinter.json
msgctxt "speed_slowdown_layers description"
msgid ""
"The first few layers are printed slower then the rest of the object, this to "
"The first few layers are printed slower than the rest of the object, this to "
"get better adhesion to the printer bed and improve the overall success rate "
"of prints. The speed is gradually increased over these layers. 4 layers of "
"speed-up is generally right for most materials and printers."
@ -942,8 +1001,8 @@ msgstr ""
msgctxt "retraction_combing description"
msgid ""
"Combing keeps the head within the interior of the print whenever possible "
"when traveling from one part of the print to another, and does not use "
"retraction. If combing is disabled the printer head moves straight from the "
"when traveling from one part of the print to another and does not use "
"retraction. If combing is disabled, the print head moves straight from the "
"start point to the end point and it will always retract."
msgstr ""
@ -992,26 +1051,6 @@ msgid ""
"nozzle diameter cubed."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_volume_retract label"
msgid "Retract-Coasting Volume"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_volume_retract description"
msgid "The volume otherwise oozed in a travel move with retraction."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_volume_move label"
msgid "Move-Coasting Volume"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_volume_move description"
msgid "The volume otherwise oozed in a travel move without retraction."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_min_volume label"
msgid "Minimal Volume Before Coasting"
@ -1022,31 +1061,8 @@ msgctxt "coasting_min_volume description"
msgid ""
"The least volume an extrusion path should have to coast the full amount. For "
"smaller extrusion paths, less pressure has been built up in the bowden tube "
"and so the coasted volume is scaled linearly."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_min_volume_retract label"
msgid "Min Volume Retract-Coasting"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_min_volume_retract description"
msgid ""
"The minimal volume an extrusion path must have in order to coast the full "
"amount before doing a retraction."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_min_volume_move label"
msgid "Min Volume Move-Coasting"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_min_volume_move description"
msgid ""
"The minimal volume an extrusion path must have in order to coast the full "
"amount before doing a travel move without retraction."
"and so the coasted volume is scaled linearly. This value should always be "
"larger than the Coasting Volume."
msgstr ""
#: fdmprinter.json
@ -1059,31 +1075,7 @@ msgctxt "coasting_speed description"
msgid ""
"The speed by which to move during coasting, relative to the speed of the "
"extrusion path. A value slightly under 100% is advised, since during the "
"coasting move, the pressure in the bowden tube drops."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_speed_retract label"
msgid "Retract-Coasting Speed"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_speed_retract description"
msgid ""
"The speed by which to move during coasting before a retraction, relative to "
"the speed of the extrusion path."
msgstr ""
#: fdmprinter.json
msgctxt "coasting_speed_move label"
msgid "Move-Coasting Speed"
msgstr ""
#: fdmprinter.json
msgctxt "coasting_speed_move description"
msgid ""
"The speed by which to move during coasting before a travel move without "
"retraction, relative to the speed of the extrusion path."
"coasting move the pressure in the bowden tube drops."
msgstr ""
#: fdmprinter.json
@ -1166,7 +1158,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "cool_min_layer_time label"
msgid "Minimal Layer Time"
msgid "Minimum Layer Time"
msgstr ""
#: fdmprinter.json
@ -1180,16 +1172,16 @@ msgstr ""
#: fdmprinter.json
msgctxt "cool_min_layer_time_fan_speed_max label"
msgid "Minimal Layer Time Full Fan Speed"
msgid "Minimum Layer Time Full Fan Speed"
msgstr ""
#: fdmprinter.json
msgctxt "cool_min_layer_time_fan_speed_max description"
msgid ""
"The minimum time spent in a layer which will cause the fan to be at maximum "
"speed. The fan speed increases linearly from minimal fan speed for layers "
"taking minimal layer time to maximum fan speed for layers taking the time "
"specified here."
"speed. The fan speed increases linearly from minimum fan speed for layers "
"taking the minimum layer time to maximum fan speed for layers taking the "
"time specified here."
msgstr ""
#: fdmprinter.json
@ -1243,7 +1235,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_type description"
msgid ""
"Where to place support structures. The placement can be restricted such that "
"Where to place support structures. The placement can be restricted so that "
"the support structures won't rest on the model, which could otherwise cause "
"scarring."
msgstr ""
@ -1279,7 +1271,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_xy_distance description"
msgid ""
"Distance of the support structure from the print, in the X/Y directions. "
"Distance of the support structure from the print in the X/Y directions. "
"0.7mm typically gives a nice distance from the print so the support does not "
"stick to the surface."
msgstr ""
@ -1352,7 +1344,7 @@ msgstr ""
msgctxt "support_conical_min_width description"
msgid ""
"Minimal width to which conical support reduces the support areas. Small "
"widths can cause the base of the support to not act well as fundament for "
"widths can cause the base of the support to not act well as foundation for "
"support above."
msgstr ""
@ -1377,8 +1369,8 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_join_distance description"
msgid ""
"The maximum distance between support blocks, in the X/Y directions, such "
"that the blocks will merge into a single block."
"The maximum distance between support blocks in the X/Y directions, so that "
"the blocks will merge into a single block."
msgstr ""
#: fdmprinter.json
@ -1401,7 +1393,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_area_smoothing description"
msgid ""
"Maximal distance in the X/Y directions of a line segment which is to be "
"Maximum distance in the X/Y directions of a line segment which is to be "
"smoothed out. Ragged lines are introduced by the join distance and support "
"bridge, which cause the machine to resonate. Smoothing the support areas "
"won't cause them to break with the constraints, except it might change the "
@ -1426,7 +1418,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_roof_height description"
msgid "The height of the support roofs. "
msgid "The height of the support roofs."
msgstr ""
#: fdmprinter.json
@ -1438,7 +1430,8 @@ msgstr ""
msgctxt "support_roof_density description"
msgid ""
"This controls how densely filled the roofs of the support will be. A higher "
"percentage results in better overhangs, which are more difficult to remove."
"percentage results in better overhangs, but makes the support more difficult "
"to remove."
msgstr ""
#: fdmprinter.json
@ -1488,7 +1481,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_use_towers label"
msgid "Use towers."
msgid "Use towers"
msgstr ""
#: fdmprinter.json
@ -1501,14 +1494,14 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_minimal_diameter label"
msgid "Minimal Diameter"
msgid "Minimum Diameter"
msgstr ""
#: fdmprinter.json
msgctxt "support_minimal_diameter description"
msgid ""
"Maximal diameter in the X/Y directions of a small area which is to be "
"supported by a specialized support tower. "
"Minimum diameter in the X/Y directions of a small area which is to be "
"supported by a specialized support tower."
msgstr ""
#: fdmprinter.json
@ -1518,7 +1511,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_tower_diameter description"
msgid "The diameter of a special tower. "
msgid "The diameter of a special tower."
msgstr ""
#: fdmprinter.json
@ -1529,7 +1522,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_tower_roof_angle description"
msgid ""
"The angle of the rooftop of a tower. Larger angles mean more pointy towers. "
"The angle of the rooftop of a tower. Larger angles mean more pointy towers."
msgstr ""
#: fdmprinter.json
@ -1540,11 +1533,11 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_pattern description"
msgid ""
"Cura supports 3 distinct types of support structure. First is a grid based "
"support structure which is quite solid and can be removed as 1 piece. The "
"second is a line based support structure which has to be peeled off line by "
"line. The third is a structure in between the other two; it consists of "
"lines which are connected in an accordeon fashion."
"Cura can generate 3 distinct types of support structure. First is a grid "
"based support structure which is quite solid and can be removed in one "
"piece. The second is a line based support structure which has to be peeled "
"off line by line. The third is a structure in between the other two; it "
"consists of lines which are connected in an accordion fashion."
msgstr ""
#: fdmprinter.json
@ -1592,7 +1585,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "support_infill_rate description"
msgid ""
"The amount of infill structure in the support, less infill gives weaker "
"The amount of infill structure in the support; less infill gives weaker "
"support which is easier to remove."
msgstr ""
@ -1619,11 +1612,14 @@ msgstr ""
#: fdmprinter.json
msgctxt "adhesion_type description"
msgid ""
"Different options that help in preventing corners from lifting due to "
"warping. Brim adds a single-layer-thick flat area around your object which "
"is easy to cut off afterwards, and it is the recommended option. Raft adds a "
"thick grid below the object and a thin interface between this and your "
"object. (Note that enabling the brim or raft disables the skirt.)"
"Different options that help to improve priming your extrusion.\n"
"Brim and Raft help in preventing corners from lifting due to warping. Brim "
"adds a single-layer-thick flat area around your object which is easy to cut "
"off afterwards, and it is the recommended option.\n"
"Raft adds a thick grid below the object and a thin interface between this "
"and your object.\n"
"The skirt is a line drawn around the first layer of the print, this helps to "
"prime your extrusion and to see if the object fits on your platform."
msgstr ""
#: fdmprinter.json
@ -1649,10 +1645,8 @@ msgstr ""
#: fdmprinter.json
msgctxt "skirt_line_count description"
msgid ""
"The skirt is a line drawn around the first layer of the. This helps to prime "
"your extruder, and to see if the object fits on your platform. Setting this "
"to 0 will disable the skirt. Multiple skirt lines can help to prime your "
"extruder better for small objects."
"Multiple skirt lines help to prime your extrusion better for small objects. "
"Setting this to 0 will disable the skirt."
msgstr ""
#: fdmprinter.json
@ -1681,6 +1675,19 @@ msgid ""
"count is set to 0 this is ignored."
msgstr ""
#: fdmprinter.json
msgctxt "brim_width label"
msgid "Brim Width"
msgstr ""
#: fdmprinter.json
msgctxt "brim_width description"
msgid ""
"The distance from the model to the end of the brim. A larger brim sticks "
"better to the build platform, but also makes your effective print area "
"smaller."
msgstr ""
#: fdmprinter.json
msgctxt "brim_line_count label"
msgid "Brim Line Count"
@ -1689,8 +1696,9 @@ msgstr ""
#: fdmprinter.json
msgctxt "brim_line_count description"
msgid ""
"The amount of lines used for a brim: More lines means a larger brim which "
"sticks better, but this also makes your effective print area smaller."
"The number of lines used for a brim. More lines means a larger brim which "
"sticks better to the build plate, but this also makes your effective print "
"area smaller."
msgstr ""
#: fdmprinter.json
@ -1721,84 +1729,84 @@ msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_layers label"
msgid "Raft Surface Layers"
msgid "Raft Top Layers"
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_layers description"
msgid ""
"The number of surface layers on top of the 2nd raft layer. These are fully "
"filled layers that the object sits on. 2 layers usually works fine."
"The number of top layers on top of the 2nd raft layer. These are fully "
"filled layers that the object sits on. 2 layers result in a smoother top "
"surface than 1."
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_thickness label"
msgid "Raft Surface Thickness"
msgid "Raft Top Layer Thickness"
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_thickness description"
msgid "Layer thickness of the surface raft layers."
msgid "Layer thickness of the top raft layers."
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_line_width label"
msgid "Raft Surface Line Width"
msgid "Raft Top Line Width"
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_line_width description"
msgid ""
"Width of the lines in the surface raft layers. These can be thin lines so "
"that the top of the raft becomes smooth."
"Width of the lines in the top surface of the raft. These can be thin lines "
"so that the top of the raft becomes smooth."
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_line_spacing label"
msgid "Raft Surface Spacing"
msgid "Raft Top Spacing"
msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_line_spacing description"
msgid ""
"The distance between the raft lines for the surface raft layers. The spacing "
"of the interface should be equal to the line width, so that the surface is "
"solid."
"The distance between the raft lines for the top raft layers. The spacing "
"should be equal to the line width, so that the surface is solid."
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_thickness label"
msgid "Raft Interface Thickness"
msgid "Raft Middle Thickness"
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_thickness description"
msgid "Layer thickness of the interface raft layer."
msgid "Layer thickness of the middle raft layer."
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_line_width label"
msgid "Raft Interface Line Width"
msgid "Raft Middle Line Width"
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_line_width description"
msgid ""
"Width of the lines in the interface raft layer. Making the second layer "
"extrude more causes the lines to stick to the bed."
"Width of the lines in the middle raft layer. Making the second layer extrude "
"more causes the lines to stick to the bed."
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_line_spacing label"
msgid "Raft Interface Spacing"
msgid "Raft Middle Spacing"
msgstr ""
#: fdmprinter.json
msgctxt "raft_interface_line_spacing description"
msgid ""
"The distance between the raft lines for the interface raft layer. The "
"spacing of the interface should be quite wide, while being dense enough to "
"support the surface raft layers."
"The distance between the raft lines for the middle raft layer. The spacing "
"of the middle should be quite wide, while being dense enough to support the "
"top raft layers."
msgstr ""
#: fdmprinter.json
@ -1855,7 +1863,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "raft_surface_speed description"
msgid ""
"The speed at which the surface raft layers are printed. This should be "
"The speed at which the surface raft layers are printed. These should be "
"printed a bit slower, so that the nozzle can slowly smooth out adjacent "
"surface lines."
msgstr ""
@ -1869,7 +1877,7 @@ msgstr ""
msgctxt "raft_interface_speed description"
msgid ""
"The speed at which the interface raft layer is printed. This should be "
"printed quite slowly, as the amount of material coming out of the nozzle is "
"printed quite slowly, as the volume of material coming out of the nozzle is "
"quite high."
msgstr ""
@ -1882,7 +1890,7 @@ msgstr ""
msgctxt "raft_base_speed description"
msgid ""
"The speed at which the base raft layer is printed. This should be printed "
"quite slowly, as the amount of material coming out of the nozzle is quite "
"quite slowly, as the volume of material coming out of the nozzle is quite "
"high."
msgstr ""
@ -1956,7 +1964,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "draft_shield_height_limitation description"
msgid "Whether to limit the height of the draft shield"
msgid "Whether or not to limit the height of the draft shield."
msgstr ""
#: fdmprinter.json
@ -1995,7 +2003,7 @@ msgstr ""
msgctxt "meshfix_union_all description"
msgid ""
"Ignore the internal geometry arising from overlapping volumes and print the "
"volumes as one. This may cause internal cavaties to disappear."
"volumes as one. This may cause internal cavities to disappear."
msgstr ""
#: fdmprinter.json
@ -2034,8 +2042,8 @@ msgctxt "meshfix_keep_open_polygons description"
msgid ""
"Normally Cura tries to stitch up small holes in the mesh and remove parts of "
"a layer with big holes. Enabling this option keeps those parts which cannot "
"be stitched. This option should be used as a last resort option when all "
"else doesn produce proper GCode."
"be stitched. This option should be used as a last resort option when "
"everything else fails to produce proper GCode."
msgstr ""
#: fdmprinter.json
@ -2053,9 +2061,9 @@ msgctxt "print_sequence description"
msgid ""
"Whether to print all objects one layer at a time or to wait for one object "
"to finish, before moving on to the next. One at a time mode is only possible "
"if all models are separated such that the whole print head can move between "
"and all models are lower than the distance between the nozzle and the X/Y "
"axles."
"if all models are separated in such a way that the whole print head can move "
"in between and all models are lower than the distance between the nozzle and "
"the X/Y axes."
msgstr ""
#: fdmprinter.json
@ -2108,7 +2116,7 @@ msgid ""
"Spiralize smooths out the Z move of the outer edge. This will create a "
"steady Z increase over the whole print. This feature turns a solid object "
"into a single walled print with a solid bottom. This feature used to be "
"called Joris in older versions."
"called Joris in older versions."
msgstr ""
#: fdmprinter.json
@ -2311,9 +2319,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "wireframe_bottom_delay description"
msgid ""
"Delay time after a downward move. Only applies to Wire Printing. Only "
"applies to Wire Printing."
msgid "Delay time after a downward move. Only applies to Wire Printing."
msgstr ""
#: fdmprinter.json
@ -2326,7 +2332,7 @@ msgctxt "wireframe_flat_delay description"
msgid ""
"Delay time between two horizontal segments. Introducing such a delay can "
"cause better adhesion to previous layers at the connection points, while too "
"large delay times cause sagging. Only applies to Wire Printing."
"long delays cause sagging. Only applies to Wire Printing."
msgstr ""
#: fdmprinter.json
@ -2391,10 +2397,10 @@ msgid ""
"Strategy for making sure two consecutive layers connect at each connection "
"point. Retraction lets the upward lines harden in the right position, but "
"may cause filament grinding. A knot can be made at the end of an upward line "
"to heighten the chance of connecting to it and to let the line cool; however "
"it may require slow printing speeds. Another strategy is to compensate for "
"the sagging of the top of an upward line; however, the lines won't always "
"fall down as predicted."
"to heighten the chance of connecting to it and to let the line cool; "
"however, it may require slow printing speeds. Another strategy is to "
"compensate for the sagging of the top of an upward line; however, the lines "
"won't always fall down as predicted."
msgstr ""
#: fdmprinter.json
@ -2459,7 +2465,7 @@ msgstr ""
#: fdmprinter.json
msgctxt "wireframe_roof_outer_delay description"
msgid ""
"Time spent at the outer perimeters of hole which is to become a roof. Larger "
"Time spent at the outer perimeters of hole which is to become a roof. Longer "
"times can ensure a better connection. Only applies to Wire Printing."
msgstr ""

2548
resources/i18n/fi/cura.po Executable file → Normal file

File diff suppressed because it is too large Load Diff

5370
resources/i18n/fi/fdmprinter.json.po Executable file → Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1320
resources/i18n/it/cura.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1320
resources/i18n/nl/cura.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,14 +1,14 @@
{
"id": "rigidbotbig",
"id": "rigidbot",
"version": 1,
"name": "RigidBot",
"manufacturer": "Other",
"author": "RBC",
"platform": "rigidbot_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
"machine_settings": {
"machine_width": { "default": 254 },
"machine_depth": { "default": 254 },
@ -32,8 +32,10 @@
},
"machine_end_gcode": {
"default": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+10 E-1 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG1 Y230 F3000 ;move Y so the head is out of the way and Plate is moved forward\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
},
}
},
"overrides": {
"layer_height": { "default": 0.2 },
"shell_thickness": { "default": 0.8 },
"wall_thickness": { "default": 0.8 },
@ -49,8 +51,8 @@
"speed_infill": { "default": 100.0, "visible": true },
"speed_topbottom": { "default": 15.0, "visible": true },
"speed_travel": { "default": 150.0, "visible": true },
"speed_layer_0": { "min_value": 0.1, "default": 15.0, "visible": true },
"infill_overlap": { "default": 10.0 },
"speed_layer_0": { "min_value": "0.1", "default": 15.0, "visible": true },
"infill_overlap": { "default": 0.04, "inherit_function": "0.1 * line_width if infill_sparse_density < 95 else 0" },
"cool_fan_enabled": { "default": false, "visible": true },
"cool_fan_speed": { "default": 0.0, "visible": true },
"skirt_line_count": { "default": 3, "active_if": { "setting": "adhesion_type", "value": "None" } },

View File

@ -5,10 +5,10 @@
"manufacturer": "Other",
"author": "RBC",
"platform": "rigidbotbig_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
"machine_settings": {
"machine_width": { "default": 400 },
"machine_depth": { "default": 300 },
@ -30,8 +30,10 @@
},
"machine_end_gcode": {
"default": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+10 E-1 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG1 Y230 F3000 ;move Y so the head is out of the way and Plate is moved forward\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
},
}
},
"overrides": {
"layer_height": { "default": 0.2 },
"shell_thickness": { "default": 0.8},
"wall_thickness": { "default": 0.8 },
@ -47,7 +49,7 @@
"speed_infill": { "default": 100.0, "visible": true },
"speed_topbottom": { "default": 15.0, "visible": true },
"speed_travel": { "default": 150.0, "visible": true },
"speed_layer_0": { "min_value": 0.1, "default": 15.0, "visible": true },
"speed_layer_0": { "min_value": "0.1", "default": 15.0, "visible": true },
"infill_overlap": { "default": 10.0 },
"cool_fan_enabled": { "default": false, "visible": true},
"cool_fan_speed": { "default": 0.0, "visible": true },

View File

@ -5,6 +5,7 @@
"manufacturer": "Other",
"author": "BQ",
"platform": "bq_hephestos_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {

View File

@ -5,6 +5,7 @@
"manufacturer": "Other",
"author": "BQ",
"platform": "bq_hephestos_2_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
@ -33,8 +34,8 @@
"default": "RepRap"
},
"machine_platform_offset": {
"default": [-6, 1320, 0]
}
"default": [6, 1320, 0]
},
"material_print_temperature": { "default": 210.0, "visible": true },
"material_bed_temperature": { "default": 0 },
"material_diameter": { "default": 1.75 },
@ -45,7 +46,7 @@
"wall_thickness": { "default": 1.2, "visible": false },
"top_bottom_thickness": { "default": 1.2, "visible": false },
"infill_sparse_density": { "default": 20.0 },
"infill_overlap": { "default": 15.0, "visible": false },
"infill_overlap": { "default": 0.06, "inherit_function": "0.15 * line_width if infill_sparse_density < 95 else 0", "visible": false },
"speed_print": { "default": 60.0 },
"speed_travel": { "default": 160.0 },
"speed_layer_0": { "default": 30.0, "visible": true },

View File

@ -5,6 +5,7 @@
"manufacturer": "Other",
"author": "BQ",
"platform": "bq_hephestos_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {

View File

@ -5,6 +5,7 @@
"manufacturer": "Other",
"author": "BQ",
"platform": "bq_witbox_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {

View File

@ -5,6 +5,7 @@
"manufacturer": "Other",
"author": "BQ",
"platform": "bq_witbox_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
@ -45,7 +46,7 @@
"wall_thickness": { "default": 1.2, "visible": false },
"top_bottom_thickness": { "default": 1.2, "visible": false },
"infill_sparse_density": { "default": 20.0 },
"infill_overlap": { "default": 15.0, "visible": false },
"infill_overlap": { "default": 0.06, "inherit_function": "0.15 * line_width if infill_sparse_density < 95 else 0", "visible": false },
"speed_print": { "default": 60.0 },
"speed_travel": { "default": 160.0 },
"speed_layer_0": { "default": 30.0, "visible": true },

View File

@ -2,12 +2,11 @@
"version": 1,
"id": "dual_extrusion",
"name": "Dual Extrusion Base File",
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
"inherits": "fdmprinter.json",
"visible": false,
"machine_extruder_trains": {
"0": {
"extruder_nr": { "default": 0 },
@ -18,7 +17,7 @@
"extruder_nr": { "default": 1 }
}
},
"machine_settings": {
"machine_use_extruder_offset_to_offset_coords": { "default": false },
@ -45,7 +44,8 @@
"max_value_warning": "150",
"default": 60,
"visible": false,
"enabled": "prime_tower_enable"
"enabled": "prime_tower_enable",
"global_only": true
}
}
},
@ -61,7 +61,8 @@
"default": 0.4,
"type": "float",
"visible": false,
"enabled": "prime_tower_enable"
"enabled": "prime_tower_enable",
"global_only": true
}
}
}
@ -88,7 +89,8 @@
"type": "int",
"default": 0,
"min_value": "0",
"max_value": "16"
"max_value": "16",
"global_only": true
},
"support_extruder_nr": {
"label": "Support Extruder",
@ -97,6 +99,7 @@
"default": 0,
"min_value": "0",
"max_value": "16",
"global_only": true,
"children": {
"support_infill_extruder_nr": {
"label": "Support Infill Extruder",
@ -104,7 +107,8 @@
"type": "int",
"default": 0,
"min_value": "0",
"max_value": "16"
"max_value": "16",
"global_only": true
},
"support_extruder_nr_layer_0": {
"label": "First Layer Support Extruder",
@ -112,7 +116,8 @@
"type": "int",
"default": 0,
"min_value": "0",
"max_value": "16"
"max_value": "16",
"global_only": true
},
"support_roof_extruder_nr": {
"label": "Support Roof Extruder",
@ -121,7 +126,8 @@
"default": 0,
"min_value": "0",
"max_value": "16",
"enabled": "support_roof_enable"
"enabled": "support_roof_enable",
"global_only": true
}
}
}
@ -132,7 +138,8 @@
"description": "Print a tower next to the print which serves to prime the material after each nozzle switch.",
"type": "boolean",
"visible": true,
"default": false
"default": false,
"global_only": true
},
"prime_tower_size": {
"label": "Prime Tower Size",
@ -144,7 +151,8 @@
"min_value": "0",
"max_value_warning": "20",
"inherit_function": "15 if prime_tower_enable else 0",
"enabled": "prime_tower_enable"
"enabled": "prime_tower_enable",
"global_only": true
},
"prime_tower_position_x": {
"label": "Prime Tower X Position",
@ -153,7 +161,10 @@
"type": "float",
"unit": "mm",
"default": 200,
"enabled": "prime_tower_enable"
"min_value_warning": "-1000",
"max_value_warning": "1000",
"enabled": "prime_tower_enable",
"global_only": true
},
"prime_tower_position_y": {
"label": "Prime Tower Y Position",
@ -162,7 +173,10 @@
"type": "float",
"unit": "mm",
"default": 200,
"enabled": "prime_tower_enable"
"min_value_warning": "-1000",
"max_value_warning": "1000",
"enabled": "prime_tower_enable",
"global_only": true
},
"prime_tower_flow": {
"label": "Prime Tower Flow",
@ -174,14 +188,16 @@
"min_value": "5",
"min_value_warning": "50",
"max_value_warning": "150",
"enabled": "prime_tower_enable"
"enabled": "prime_tower_enable",
"global_only": true
},
"prime_tower_wipe_enabled": {
"label": "Wipe Nozzle on Prime tower",
"description": "After printing the prime tower with the one nozzle, wipe the oozed material from the other nozzle off on the prime tower.",
"type": "boolean",
"default": false,
"enabled": "prime_tower_enable"
"enabled": "prime_tower_enable",
"global_only": true
},
"multiple_mesh_overlap": {
"label": "Dual Extrusion Overlap",
@ -191,13 +207,15 @@
"unit": "mm",
"default": 0.15,
"min_value": "0",
"max_value_warning": "1.0"
"max_value_warning": "1.0",
"global_only": true
},
"ooze_shield_enabled": {
"label": "Enable Ooze Shield",
"description": "Enable exterior ooze shield. This will create a shell around the object which is likely to wipe a second nozzle if it's at the same height as the first nozzle.",
"type": "boolean",
"default": false
"default": false,
"global_only": true
},
"ooze_shield_angle": {
"label": "Ooze Shield Angle",
@ -208,7 +226,8 @@
"max_value": "90",
"default": 60,
"visible": false,
"enabled": "ooze_shield_enabled"
"enabled": "ooze_shield_enabled",
"global_only": true
},
"ooze_shield_dist": {
"label": "Ooze Shields Distance",
@ -219,21 +238,36 @@
"max_value_warning": "30",
"default": 2,
"visible": false,
"enabled": "ooze_shield_enabled"
"enabled": "ooze_shield_enabled",
"global_only": true
}
}
},
"material": {
"settings": {
"material_standby_temperature": {
"label": "Standby Temperature",
"description": "The temperature of the nozzle when another nozzle is currently used for printing.",
"unit": "°C",
"type": "float",
"default": 150,
"min_value": "0",
"max_value_warning": "260",
"global_only": "True",
"visible": false
},
"switch_extruder_retraction_amount": {
"label": "Nozzle Switch Retraction Distance",
"description": "The amount of retraction: Set at 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
"unit": "mm",
"type": "float",
"default": 16,
"min_value_warning": "0",
"max_value_warning": "100",
"visible": false,
"inherit_function": "machine_heat_zone_length",
"enabled": "retraction_enable"
"enabled": "retraction_enable",
"global_only": true
},
"switch_extruder_retraction_speeds": {
"label": "Nozzle Switch Retraction Speed",
@ -241,9 +275,12 @@
"unit": "mm/s",
"type": "float",
"default": 20,
"min_value": "0.1",
"max_value_warning": "300",
"visible": false,
"inherit": false,
"enabled": "retraction_enable",
"global_only": true,
"children": {
"switch_extruder_retraction_speed": {
"label": "Nozzle Switch Retract Speed",
@ -251,8 +288,11 @@
"unit": "mm/s",
"type": "float",
"default": 20,
"min_value": "0.1",
"max_value_warning": "300",
"visible": false,
"enabled": "retraction_enable"
"enabled": "retraction_enable",
"global_only": true
},
"switch_extruder_prime_speed": {
"label": "Nozzle Switch Prime Speed",
@ -260,8 +300,11 @@
"unit": "mm/s",
"type": "float",
"default": 20,
"min_value": "0.1",
"max_value_warning": "300",
"visible": false,
"enabled": "retraction_enable"
"enabled": "retraction_enable",
"global_only": true
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
"author": "Other",
"icon": "icon_ultimaker.png",
"platform": "grr_neo_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"visible": "true",

View File

@ -1,18 +1,18 @@
{
"id": "innovo-inventor",
"name": "Innovo INVENTOR",
"manufacturer": "INNOVO",
"author": "AR",
"version": 1,
"icon": "",
"name": "Innovo INVENTOR",
"manufacturer": "Other",
"author": "AR",
"platform": "inventor_platform.stl",
"platform_texture": "",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"machine_settings": {
"machine_width": {"default": 340},
"machine_height": {"default": 290},
"machine_depth": {"default": 300},
"machine_heated_bed": { "default": true }
"machine_heated_bed": { "default": true},
"machine_center_is_zero": {"default": false},
"machine_nozzle_size": {"default": 0.4},
"machine_head_shape_min_x": {"default": 43.7},
@ -24,7 +24,8 @@
"machine_nozzle_offset_y_1": {"default": 15},
"machine_gcode_flavor": {"default": "RepRap (Marlin/Sprinter)"},
"machine_start_gcode": {"default": "G28 ; Home extruder\nM107 ; Turn off fan\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\n{IF_BED}M190 S{BED}\n{IF_EXT0}M104 T0 S{TEMP0}\n{IF_EXT0}M109 T0 S{TEMP0}\n{IF_EXT1}M104 T1 S{TEMP1}\n{IF_EXT1}M109 T1 S{TEMP1}\nG32 S3 ; auto level\nG92 E0 ; Reset extruder position"},
"machine_end_gcode": {"default": "M104 S0\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors"}
"machine_end_gcode": {"default": "M104 S0\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors"},
"machine_platform_offset": {"default": [-180, -0.25, 160]}
},
"overrides": {
@ -44,6 +45,6 @@
"speed_topbottom": { "default": 30.0, "visible": true },
"speed_travel": { "default": 150.0, "visible": true },
"speed_layer_0": { "min_value": 0.1, "default": 30.0, "visible": true },
"infill_overlap": { "default": 10.0 }
"infill_overlap": { "default": 0.04, "inherit_function": "0.1 * line_width if infill_sparse_density < 95 else 0" }
}
}
}

View File

@ -0,0 +1,39 @@
{
"id": "m180",
"version": 1,
"name": "Malyan M180",
"manufacturer": "Other",
"icon": "icon_ultimaker.png",
"platform": "",
"file_formats": "application/x3g",
"inherits": "fdmprinter.json",
"machine_settings": {
"machine_width": { "default": 230 },
"machine_height": { "default": 165 },
"machine_depth": { "default": 145 },
"machine_center_is_zero": { "default": true },
"machine_nozzle_size": { "default": 0.4, "min_value": "0.001" },
"machine_head_with_fans_polygon": {
"default": [
[ -75, 35 ],
[ -75, -18 ],
[ 18, -18 ],
[ 18, 35 ]
]
},
"gantry_height": { "default": 55 },
"machine_gcode_flavor": { "default": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": { "default": "M136\nM73 P0\nM103\nG21\nG90\nM320\n;(**** begin homing ****)\nG162 X Y F4000\nG161 Z F3500\nG92 Z-5\nG1 Z0.0\nG161 Z F100\nM132 X Y Z A B\n;(**** end homing ****)\nG92 X147 Y66 Z5\nG1 X105 Y-60 Z10 F4000.0\nG130 X127 Y127 A127 B127\nG0 X105 Y-60\nG1 Z0.3 F300\nG92 E0\nG1 X100 E10 F300\nG92 E0\nG1 Z0.0 F300\nM320" },
"machine_end_gcode": { "default": "G92 Z0\nG1 Z10 F400\nM18\nM109 S0 T0\nM104 S0 T0\nM73 P100 (end build progress)\nG162 X Y F3000\nM18" }
},
"overrides": {
"material_bed_temperature": { "visible": "True" },
"material_diameter": {
"default": 1.75,
"min_value_warning": "1.5",
"max_value_warning": "2.0"
}
}
}

View File

@ -6,7 +6,7 @@
"author": "Other",
"icon": "icon_ultimaker2.png",
"platform": "makerstarter_platform.stl",
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj",
"inherits": "fdmprinter.json",
"overrides": {

View File

@ -6,7 +6,7 @@
"author": "Other",
"icon": "icon_ultimaker2.png",
"platform": "prusai3_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
@ -16,6 +16,7 @@
"machine_depth": { "default": 200 },
"machine_center_is_zero": { "default": false },
"machine_nozzle_size": { "default": 0.4 },
"material_diameter": { "default": 1.75 },
"machine_nozzle_heat_up_speed": { "default": 2.0 },
"machine_nozzle_cool_down_speed": { "default": 2.0 },
"machine_head_shape_min_x": { "default": 75 },

View File

@ -0,0 +1,36 @@
{
"id": "prusa_i3_xl",
"version": 1,
"name": "Prusa i3 xl",
"manufacturer": "Other",
"author": "Other",
"icon": "icon_ultimaker2.png",
"platform": "prusai3_xl_platform.stl",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"overrides": {
"machine_heated_bed": { "default": true },
"machine_width": { "default": 200 },
"machine_height": { "default": 200 },
"machine_depth": { "default": 270 },
"machine_center_is_zero": { "default": false },
"machine_nozzle_size": { "default": 0.4 },
"material_diameter": { "default": 1.75 },
"machine_nozzle_heat_up_speed": { "default": 2.0 },
"machine_nozzle_cool_down_speed": { "default": 2.0 },
"machine_head_shape_min_x": { "default": 75 },
"machine_head_shape_min_y": { "default": 18 },
"machine_head_shape_max_x": { "default": 18 },
"machine_head_shape_max_y": { "default": 35 },
"machine_nozzle_gantry_distance": { "default": 55 },
"machine_gcode_flavor": { "default": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_end_gcode": {
"default": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
}
}
}

View File

@ -0,0 +1,15 @@
{
"id": "ultimaker_base",
"version": 1,
"visible": false,
"name": "Ultimaker",
"manufacturer": "Ultimaker",
"author": "Ultimaker",
"inherits": "fdmprinter.json",
"machine_preferences": {
"prefered_profile": "Normal Quality",
"prefered_variant": "0.4 mm",
"prefered_material": "PLA"
}
}

View File

@ -1,23 +1,27 @@
{
"id": "ultimaker2",
"version": 1,
"version": 1,
"name": "Ultimaker 2",
"manufacturer": "Ultimaker",
"author": "Ultimaker",
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"file_formats": "text/x-gcode",
"inherits": "fdmprinter.json",
"inherits": "ultimaker.json",
"machine_extruder_trains": {
"0": {
"machine_nozzle_heat_up_speed": {
"default": 2.0
"pages": [
"SelectUpgradedPartsUM2"
],
"machine_extruder_trains": [
{
"machine_nozzle_heat_up_speed": {
"default": 2.0
},
"machine_nozzle_cool_down_speed": {
"default": 2.0
"machine_nozzle_cool_down_speed": {
"default": 2.0
},
"machine_nozzle_tip_outer_diameter": {
"default": 1
@ -28,12 +32,12 @@
"machine_nozzle_expansion_angle": {
"default": 45
},
"machine_heat_zone_length": {
"machine_heat_zone_length": {
"default": 16
}
}
},
"overrides": {
],
"machine_settings": {
"machine_start_gcode" : { "default": "" },
"machine_end_gcode" : { "default": "" },
"machine_width": { "default": 230 },
@ -76,14 +80,20 @@
[[ 115.0, -112.5], [ 108.0, -112.5], [ 110.0, -104.5], [ 115.0, -104.5]]
]},
"machine_platform_offset": { "default": [9.0, 0.0, 0.0] },
"machine_nozzle_tip_outer_diameter": { "default": 1.0 },
"machine_nozzle_head_distance": { "default": 3.0 },
"machine_nozzle_expansion_angle": { "default": 45 },
"machine_nozzle_expansion_angle": { "default": 45 }
},
"overrides": {
"material_print_temperature": { "enabled": "False" },
"material_bed_temperature": { "enabled": "False" },
"material_diameter": { "enabled": "False" },
"material_flow": { "enabled": "False" }
"material_flow": { "enabled": "False" },
"retraction_amount": { "enabled": "False" },
"retraction_speed": { "enabled": "False" },
"retraction_retract_speed": { "enabled": "False" },
"retraction_prime_speed": { "enabled": "False" }
}
}

View File

@ -7,10 +7,14 @@
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Extendedbackplate.png",
"file_formats": "text/x-gcode",
"inherits": "ultimaker2.json",
"overrides": {
"pages": [
"SelectUpgradedPartsUM2"
],
"machine_settings": {
"machine_width": { "default": 230 },
"machine_depth": { "default": 225 },
"machine_height": { "default": 315 }

View File

@ -0,0 +1,20 @@
{
"id": "ultimaker2_extended_olsson_base",
"version": 1,
"name": "Ultimaker 2 Extended with Olsson Block",
"manufacturer": "Ultimaker",
"author": "Ultimaker",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"visible": false,
"file_formats": "text/x-gcode",
"inherits": "ultimaker2.json",
"machine_settings": {
"machine_width": { "default": 230 },
"machine_depth": { "default": 225 },
"machine_height": { "default": 310 },
"machine_show_variants": { "default": true },
"gantry_height": { "default": 50 }
}
}

View File

@ -0,0 +1,17 @@
{
"id": "ultimaker2_extended_olsson",
"version": 1,
"name": "Ultimaker 2 Extended with Olsson Block",
"manufacturer": "Ultimaker",
"author": "Ultimaker",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"visible": false,
"file_formats": "text/x-gcode",
"inherits": "ultimaker2_extended_olsson.json",
"variant": "0.25 mm",
"profiles_machine": "ultimaker2_olsson",
"machine_settings": {
"machine_nozzle_size": { "default": 0.25 }
}
}

View File

@ -0,0 +1,18 @@
{
"id": "ultimaker2_extended_olsson",
"version": 1,
"name": "Ultimaker 2 Extended with Olsson Block",
"manufacturer": "Ultimaker",
"author": "Ultimaker",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"visible": false,
"file_formats": "text/x-gcode",
"inherits": "ultimaker2_extended_olsson.json",
"variant": "0.4 mm",
"profiles_machine": "ultimaker2_olsson",
"machine_settings": {
"machine_nozzle_size": { "default": 0.40 }
}
}

Some files were not shown because too many files have changed in this diff Show More