mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-12-10 15:28:56 -07:00
Move src to cura so we can use the same package for installed and source
Contributes to #41 Contributes to #42
This commit is contained in:
parent
70f5dede95
commit
ae71c309ac
10 changed files with 2 additions and 2 deletions
|
|
@ -1,127 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.View.Renderer import Renderer
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Application import Application
|
||||
from UM.Resources import Resources
|
||||
from UM.Mesh.MeshData import MeshData
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
|
||||
import numpy
|
||||
|
||||
class BuildVolume(SceneNode):
|
||||
VolumeOutlineColor = Color(12, 169, 227, 255)
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._width = 0
|
||||
self._height = 0
|
||||
self._depth = 0
|
||||
|
||||
self._material = None
|
||||
|
||||
self._grid_mesh = None
|
||||
self._grid_material = None
|
||||
|
||||
self._disallowed_areas = []
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
|
||||
def setWidth(self, width):
|
||||
self._width = width
|
||||
|
||||
def setHeight(self, height):
|
||||
self._height = height
|
||||
|
||||
def setDepth(self, depth):
|
||||
self._depth = depth
|
||||
|
||||
def setDisallowedAreas(self, areas):
|
||||
self._disallowed_areas = areas
|
||||
|
||||
def render(self, renderer):
|
||||
if not self.getMeshData():
|
||||
return True
|
||||
|
||||
if not self._material:
|
||||
self._material = renderer.createMaterial(
|
||||
Resources.getPath(Resources.ShadersLocation, "basic.vert"),
|
||||
Resources.getPath(Resources.ShadersLocation, "vertexcolor.frag")
|
||||
)
|
||||
self._grid_material = renderer.createMaterial(
|
||||
Resources.getPath(Resources.ShadersLocation, "basic.vert"),
|
||||
Resources.getPath(Resources.ShadersLocation, "grid.frag")
|
||||
)
|
||||
self._grid_material.setUniformValue("u_gridColor0", Color(245, 245, 245, 255))
|
||||
self._grid_material.setUniformValue("u_gridColor1", Color(205, 202, 201, 255))
|
||||
|
||||
renderer.queueNode(self, material = self._material, mode = Renderer.RenderLines)
|
||||
renderer.queueNode(self, mesh = self._grid_mesh, material = self._grid_material)
|
||||
if self._disallowed_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._disallowed_area_mesh, material = self._material)
|
||||
return True
|
||||
|
||||
def rebuild(self):
|
||||
if self._width == 0 or self._height == 0 or self._depth == 0:
|
||||
return
|
||||
|
||||
minW = -self._width / 2
|
||||
maxW = self._width / 2
|
||||
minH = 0.0
|
||||
maxH = self._height
|
||||
minD = -self._depth / 2
|
||||
maxD = self._depth / 2
|
||||
|
||||
mb = MeshBuilder()
|
||||
|
||||
mb.addLine(Vector(minW, minH, minD), Vector(maxW, minH, minD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(minW, minH, minD), Vector(minW, maxH, minD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(minW, maxH, minD), Vector(maxW, maxH, minD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(maxW, minH, minD), Vector(maxW, maxH, minD), color = self.VolumeOutlineColor)
|
||||
|
||||
mb.addLine(Vector(minW, minH, maxD), Vector(maxW, minH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(minW, minH, maxD), Vector(minW, maxH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(minW, maxH, maxD), Vector(maxW, maxH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(maxW, minH, maxD), Vector(maxW, maxH, maxD), color = self.VolumeOutlineColor)
|
||||
|
||||
mb.addLine(Vector(minW, minH, minD), Vector(minW, minH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(maxW, minH, minD), Vector(maxW, minH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(minW, maxH, minD), Vector(minW, maxH, maxD), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(maxW, maxH, minD), Vector(maxW, maxH, maxD), color = self.VolumeOutlineColor)
|
||||
|
||||
self.setMeshData(mb.getData())
|
||||
|
||||
mb = MeshBuilder()
|
||||
mb.addQuad(
|
||||
Vector(minW, minH, maxD),
|
||||
Vector(maxW, minH, maxD),
|
||||
Vector(maxW, minH, minD),
|
||||
Vector(minW, minH, minD)
|
||||
)
|
||||
self._grid_mesh = mb.getData()
|
||||
for n in range(0, 6):
|
||||
v = self._grid_mesh.getVertex(n)
|
||||
self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2])
|
||||
|
||||
if self._disallowed_areas:
|
||||
mb = MeshBuilder()
|
||||
for area in self._disallowed_areas:
|
||||
mb.addQuad(
|
||||
area[0],
|
||||
area[1],
|
||||
area[2],
|
||||
area[3],
|
||||
color = Color(174, 174, 174, 255)
|
||||
)
|
||||
|
||||
self._disallowed_area_mesh = mb.getData()
|
||||
else:
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self._aabb = AxisAlignedBox(minimum = Vector(minW, minH - 1.0, minD), maximum = Vector(maxW, maxH, maxD))
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
from PyQt5.QtCore import QVariantAnimation, QEasingCurve
|
||||
from PyQt5.QtGui import QVector3D
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
class CameraAnimation(QVariantAnimation):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._camera_tool = None
|
||||
self.setDuration(500)
|
||||
self.setEasingCurve(QEasingCurve.InOutQuad)
|
||||
|
||||
def setCameraTool(self, camera_tool):
|
||||
self._camera_tool = camera_tool
|
||||
|
||||
def setStart(self, start):
|
||||
self.setStartValue(QVector3D(start.x, start.y, start.z))
|
||||
|
||||
def setTarget(self, target):
|
||||
self.setEndValue(QVector3D(target.x, target.y, target.z))
|
||||
|
||||
def updateCurrentValue(self, value):
|
||||
self._camera_tool.setOrigin(Vector(value.x(), value.y(), value.z()))
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Application import Application
|
||||
from UM.Math.Polygon import Polygon
|
||||
|
||||
import numpy
|
||||
|
||||
from . import ConvexHullNode
|
||||
|
||||
class ConvexHullJob(Job):
|
||||
def __init__(self, node):
|
||||
super().__init__()
|
||||
|
||||
self._node = node
|
||||
|
||||
def run(self):
|
||||
if not self._node or not self._node.getMeshData():
|
||||
return
|
||||
|
||||
mesh = self._node.getMeshData()
|
||||
vertexData = mesh.getTransformed(self._node.getWorldTransformation()).getVertices()
|
||||
|
||||
hull = Polygon(numpy.rint(vertexData[:, [0, 2]]).astype(int))
|
||||
|
||||
# First, calculate the normal convex hull around the points
|
||||
hull = hull.getConvexHull()
|
||||
# Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array([[-1, -1], [-1, 1], [1, 1], [1, -1]], numpy.float32)))
|
||||
|
||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot())
|
||||
|
||||
self._node._convex_hull = hull
|
||||
delattr(self._node, "_convex_hull_job")
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
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
|
||||
|
||||
import numpy
|
||||
|
||||
class ConvexHullNode(SceneNode):
|
||||
def __init__(self, node, hull, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
|
||||
self._material = None
|
||||
|
||||
self._original_parent = parent
|
||||
|
||||
self._inherit_orientation = False
|
||||
self._inherit_scale = False
|
||||
|
||||
self._node = node
|
||||
self._node.transformationChanged.connect(self._onNodePositionChanged)
|
||||
self._node.parentChanged.connect(self._onNodeParentChanged)
|
||||
#self._onNodePositionChanged(self._node)
|
||||
|
||||
self._hull = hull
|
||||
|
||||
hull_points = self._hull.getPoints()
|
||||
center = (hull_points.min(0) + hull_points.max(0)) / 2.0
|
||||
|
||||
mesh = MeshData()
|
||||
mesh.addVertex(center[0], 0.1, center[1])
|
||||
|
||||
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.addIndices(numpy.array(indices, numpy.int32))
|
||||
|
||||
self.setMeshData(mesh)
|
||||
|
||||
def render(self, renderer):
|
||||
if not self._material:
|
||||
self._material = renderer.createMaterial(Resources.getPath(Resources.ShadersLocation, "basic.vert"), Resources.getPath(Resources.ShadersLocation, "color.frag"))
|
||||
|
||||
self._material.setUniformValue("u_color", Color(35, 35, 35, 128))
|
||||
|
||||
renderer.queueNode(self, material = self._material, transparent = True)
|
||||
|
||||
return True
|
||||
|
||||
def _onNodePositionChanged(self, node):
|
||||
#self.setPosition(node.getWorldPosition())
|
||||
if hasattr(node, "_convex_hull"):
|
||||
delattr(node, "_convex_hull")
|
||||
self.setParent(None)
|
||||
|
||||
|
||||
#self._node.transformationChanged.disconnect(self._onNodePositionChanged)
|
||||
#self._node.parentChanged.disconnect(self._onNodeParentChanged)
|
||||
|
||||
def _onNodeParentChanged(self, node):
|
||||
if node.getParent():
|
||||
self.setParent(self._original_parent)
|
||||
else:
|
||||
self.setParent(None)
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.Platform import Platform
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.ToolHandle import ToolHandle
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Mesh.WriteMeshJob import WriteMeshJob
|
||||
from UM.Mesh.ReadMeshJob import ReadMeshJob
|
||||
from UM.Logger import Logger
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.Scene.BoxRenderer import BoxRenderer
|
||||
from UM.Scene.Selection import Selection
|
||||
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import PlatformPhysics
|
||||
from . import BuildVolume
|
||||
from . import CameraAnimation
|
||||
from . import PrintInformation
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, Qt, pyqtSignal, pyqtProperty
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import os.path
|
||||
import numpy
|
||||
numpy.seterr(all="ignore")
|
||||
|
||||
class CuraApplication(QtApplication):
|
||||
def __init__(self):
|
||||
Resources.addResourcePath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
|
||||
if not hasattr(sys, "frozen"):
|
||||
Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
|
||||
|
||||
super().__init__(name = "cura", version = "15.05.90")
|
||||
|
||||
self.setRequiredPlugins([
|
||||
"CuraEngineBackend",
|
||||
"MeshView",
|
||||
"LayerView",
|
||||
"STLReader",
|
||||
"SelectionTool",
|
||||
"CameraTool",
|
||||
"GCodeWriter",
|
||||
"LocalFileStorage"
|
||||
])
|
||||
self._physics = None
|
||||
self._volume = None
|
||||
self._platform = None
|
||||
self._output_devices = {}
|
||||
self._print_information = None
|
||||
self._i18n_catalog = None
|
||||
|
||||
self.activeMachineChanged.connect(self._onActiveMachineChanged)
|
||||
|
||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||
|
||||
## Handle loading of all plugin types (and the backend explicitly)
|
||||
# \sa PluginRegistery
|
||||
def _loadPlugins(self):
|
||||
self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
|
||||
if not hasattr(sys, "frozen"):
|
||||
self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
|
||||
|
||||
self._plugin_registry.loadPlugins({ "type": "logger"})
|
||||
self._plugin_registry.loadPlugins({ "type": "storage_device" })
|
||||
self._plugin_registry.loadPlugins({ "type": "view" })
|
||||
self._plugin_registry.loadPlugins({ "type": "mesh_reader" })
|
||||
self._plugin_registry.loadPlugins({ "type": "mesh_writer" })
|
||||
self._plugin_registry.loadPlugins({ "type": "tool" })
|
||||
self._plugin_registry.loadPlugins({ "type": "extension" })
|
||||
|
||||
self._plugin_registry.loadPlugin("CuraEngineBackend")
|
||||
|
||||
def addCommandLineOptions(self, parser):
|
||||
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
|
||||
|
||||
def run(self):
|
||||
self._i18n_catalog = i18nCatalog("cura");
|
||||
|
||||
self.addOutputDevice("local_file", {
|
||||
"id": "local_file",
|
||||
"function": self._writeToLocalFile,
|
||||
"description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
|
||||
"icon": "save",
|
||||
"priority": 0
|
||||
})
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))
|
||||
|
||||
controller = self.getController()
|
||||
|
||||
controller.setActiveView("MeshView")
|
||||
controller.setCameraTool("CameraTool")
|
||||
controller.setSelectionTool("SelectionTool")
|
||||
|
||||
t = controller.getTool("TranslateTool")
|
||||
if t:
|
||||
t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])
|
||||
|
||||
Selection.selectionChanged.connect(self.onSelectionChanged)
|
||||
|
||||
root = controller.getScene().getRoot()
|
||||
self._platform = Platform(root)
|
||||
|
||||
self._volume = BuildVolume.BuildVolume(root)
|
||||
|
||||
self.getRenderer().setLightPosition(Vector(0, 150, 0))
|
||||
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
|
||||
|
||||
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
|
||||
|
||||
camera = Camera("3d", root)
|
||||
camera.setPosition(Vector(-150, 150, 300))
|
||||
camera.setPerspective(True)
|
||||
camera.lookAt(Vector(0, 0, 0))
|
||||
|
||||
self._camera_animation = CameraAnimation.CameraAnimation()
|
||||
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
||||
|
||||
controller.getScene().setActiveCamera("3d")
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))
|
||||
|
||||
self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
|
||||
self.initializeEngine()
|
||||
|
||||
self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)
|
||||
|
||||
if self.getMachines():
|
||||
active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
|
||||
if active_machine_pref:
|
||||
for machine in self.getMachines():
|
||||
if machine.getName() == active_machine_pref:
|
||||
self.setActiveMachine(machine)
|
||||
|
||||
if not self.getActiveMachine():
|
||||
self.setActiveMachine(self.getMachines()[0])
|
||||
else:
|
||||
self.requestAddPrinter.emit()
|
||||
|
||||
self._removableDrivesChanged()
|
||||
if self._engine.rootObjects:
|
||||
self.closeSplash()
|
||||
|
||||
for file in self.getCommandLineOption("file", []):
|
||||
job = ReadMeshJob(os.path.abspath(file))
|
||||
job.start()
|
||||
|
||||
self.exec_()
|
||||
|
||||
def registerObjects(self, engine):
|
||||
engine.rootContext().setContextProperty("Printer", self)
|
||||
self._print_information = PrintInformation.PrintInformation()
|
||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||
|
||||
def onSelectionChanged(self):
|
||||
if Selection.hasSelection():
|
||||
if not self.getController().getActiveTool():
|
||||
self.getController().setActiveTool("TranslateTool")
|
||||
|
||||
self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
|
||||
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
||||
self._camera_animation.start()
|
||||
else:
|
||||
if self.getController().getActiveTool():
|
||||
self.getController().setActiveTool(None)
|
||||
|
||||
requestAddPrinter = pyqtSignal()
|
||||
|
||||
## Remove an object from the scene
|
||||
@pyqtSlot("quint64")
|
||||
def deleteObject(self, object_id):
|
||||
object = self.getController().getScene().findObject(object_id)
|
||||
|
||||
if object:
|
||||
op = RemoveSceneNodeOperation(object)
|
||||
op.push()
|
||||
|
||||
## Create a number of copies of existing object.
|
||||
@pyqtSlot("quint64", int)
|
||||
def multiplyObject(self, object_id, count):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
|
||||
if node:
|
||||
op = GroupedOperation()
|
||||
for i in range(count):
|
||||
new_node = SceneNode()
|
||||
new_node.setMeshData(node.getMeshData())
|
||||
new_node.setScale(node.getScale())
|
||||
new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
|
||||
new_node.setSelectable(True)
|
||||
op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
|
||||
op.push()
|
||||
|
||||
## Center object on platform.
|
||||
@pyqtSlot("quint64")
|
||||
def centerObject(self, object_id):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
|
||||
if node:
|
||||
transform = node.getLocalTransformation()
|
||||
transform.setTranslation(Vector(0, 0, 0))
|
||||
op = SetTransformOperation(node, transform)
|
||||
op.push()
|
||||
|
||||
## Delete all mesh data on the scene.
|
||||
@pyqtSlot()
|
||||
def deleteAll(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
|
||||
for node in nodes:
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
|
||||
op.push()
|
||||
|
||||
## Reset all translation on nodes with mesh data.
|
||||
@pyqtSlot()
|
||||
def resetAllTranslation(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
|
||||
for node in nodes:
|
||||
transform = node.getLocalTransformation()
|
||||
transform.setTranslation(Vector(0, 0, 0))
|
||||
op.addOperation(SetTransformOperation(node, transform))
|
||||
|
||||
op.push()
|
||||
|
||||
## Reset all transformations on nodes with mesh data.
|
||||
@pyqtSlot()
|
||||
def resetAll(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
|
||||
for node in nodes:
|
||||
transform = Matrix()
|
||||
op.addOperation(SetTransformOperation(node, transform))
|
||||
|
||||
op.push()
|
||||
|
||||
## Reload all mesh data on the screen from file.
|
||||
@pyqtSlot()
|
||||
def reloadAll(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
file_name = node.getMeshData().getFileName()
|
||||
|
||||
job = ReadMeshJob(file_name)
|
||||
job.finished.connect(lambda j: node.setMeshData(j.getResult()))
|
||||
job.start()
|
||||
|
||||
## Get logging data of the backend engine
|
||||
# \returns \type{string} Logging data
|
||||
@pyqtSlot(result=str)
|
||||
def getEngineLog(self):
|
||||
log = ""
|
||||
|
||||
for entry in self.getBackend().getLog():
|
||||
log += entry.decode()
|
||||
|
||||
return log
|
||||
|
||||
outputDevicesChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty("QVariantMap", notify = outputDevicesChanged)
|
||||
def outputDevices(self):
|
||||
return self._output_devices
|
||||
|
||||
@pyqtProperty("QStringList", notify = outputDevicesChanged)
|
||||
def outputDeviceNames(self):
|
||||
return self._output_devices.keys()
|
||||
|
||||
@pyqtSlot(str, result = "QVariant")
|
||||
def getSettingValue(self, key):
|
||||
if not self.getActiveMachine():
|
||||
return None
|
||||
|
||||
return self.getActiveMachine().getSettingValueByKey(key)
|
||||
|
||||
## Change setting by key value pair
|
||||
@pyqtSlot(str, "QVariant")
|
||||
def setSettingValue(self, key, value):
|
||||
if not self.getActiveMachine():
|
||||
return
|
||||
|
||||
self.getActiveMachine().setSettingValueByKey(key, value)
|
||||
|
||||
## Add an output device that can be written to.
|
||||
#
|
||||
# \param id \type{string} The identifier used to identify the device.
|
||||
# \param device \type{StorageDevice} A dictionary of device information.
|
||||
# It should contains the following:
|
||||
# - function: A function to be called when trying to write to the device. Will be passed the device id as first parameter.
|
||||
# - description: A translated string containing a description of what happens when writing to the device.
|
||||
# - icon: The icon to use to represent the device.
|
||||
# - priority: The priority of the device. The device with the highest priority will be used as the default device.
|
||||
def addOutputDevice(self, id, device):
|
||||
self._output_devices[id] = device
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
## Remove output device
|
||||
# \param id \type{string} The identifier used to identify the device.
|
||||
# \sa PrinterApplication::addOutputDevice()
|
||||
def removeOutputDevice(self, id):
|
||||
if id in self._output_devices:
|
||||
del self._output_devices[id]
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def writeToOutputDevice(self, device):
|
||||
self._output_devices[device]["function"](device)
|
||||
|
||||
writeToLocalFileRequested = pyqtSignal()
|
||||
|
||||
def _writeToLocalFile(self, device):
|
||||
self.writeToLocalFileRequested.emit()
|
||||
|
||||
def _writeToSD(self, device):
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
|
||||
try:
|
||||
path = self.getStorageDevice("LocalFileStorage").getRemovableDrives()[device]
|
||||
except KeyError:
|
||||
Logger.log("e", "Tried to write to unknown SD card %s", device)
|
||||
return
|
||||
|
||||
filename = os.path.join(path, node.getName()[0:node.getName().rfind(".")] + ".gcode")
|
||||
|
||||
job = WriteMeshJob(filename, node.getMeshData())
|
||||
job._sdcard = device
|
||||
job.start()
|
||||
job.finished.connect(self._onWriteToSDFinished)
|
||||
return
|
||||
|
||||
def _removableDrivesChanged(self):
|
||||
drives = self.getStorageDevice("LocalFileStorage").getRemovableDrives()
|
||||
for drive in drives:
|
||||
if drive not in self._output_devices:
|
||||
self.addOutputDevice(drive, {
|
||||
"id": drive,
|
||||
"function": self._writeToSD,
|
||||
"description": self._i18n_catalog.i18nc("Save button tooltip. {0} is sd card name", "Save to SD Card {0}".format(drive)),
|
||||
"icon": "save_sd",
|
||||
"priority": 1
|
||||
})
|
||||
|
||||
drives_to_remove = []
|
||||
for device in self._output_devices:
|
||||
if device not in drives:
|
||||
if self._output_devices[device]["function"] == self._writeToSD:
|
||||
drives_to_remove.append(device)
|
||||
|
||||
for drive in drives_to_remove:
|
||||
self.removeOutputDevice(drive)
|
||||
|
||||
def _onActiveMachineChanged(self):
|
||||
machine = self.getActiveMachine()
|
||||
if machine:
|
||||
Preferences.getInstance().setValue("cura/active_machine", machine.getName())
|
||||
|
||||
self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
|
||||
self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
|
||||
self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))
|
||||
|
||||
disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
|
||||
areas = []
|
||||
if disallowed_areas:
|
||||
|
||||
for area in disallowed_areas:
|
||||
polygon = []
|
||||
polygon.append(Vector(area[0][0], 0.2, area[0][1]))
|
||||
polygon.append(Vector(area[1][0], 0.2, area[1][1]))
|
||||
polygon.append(Vector(area[2][0], 0.2, area[2][1]))
|
||||
polygon.append(Vector(area[3][0], 0.2, area[3][1]))
|
||||
areas.append(polygon)
|
||||
self._volume.setDisallowedAreas(areas)
|
||||
|
||||
self._volume.rebuild()
|
||||
|
||||
if self.getController().getTool("ScaleTool"):
|
||||
self.getController().getTool("ScaleTool").setMaximumBounds(self._volume.getBoundingBox())
|
||||
|
||||
offset = machine.getSettingValueByKey("machine_platform_offset")
|
||||
if offset:
|
||||
self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
|
||||
else:
|
||||
self._platform.setPosition(Vector(0.0, 0.0, 0.0))
|
||||
|
||||
def _onWriteToSDFinished(self, job):
|
||||
message = Message(self._i18n_catalog.i18nc("Saved to SD message, {0} is sdcard, {1} is filename", "Saved to SD Card {0} as {1}").format(job._sdcard, job.getFileName()))
|
||||
message.addAction(
|
||||
"eject",
|
||||
self._i18n_catalog.i18nc("Message action", "Eject"),
|
||||
"eject",
|
||||
self._i18n_catalog.i18nc("Message action tooltip, {0} is sdcard", "Eject SD Card {0}".format(job._sdcard))
|
||||
)
|
||||
message._sdcard = job._sdcard
|
||||
message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
message.show()
|
||||
|
||||
def _onMessageActionTriggered(self, message, action):
|
||||
if action == "eject":
|
||||
self.getStorageDevice("LocalFileStorage").ejectRemovableDrive(message._sdcard)
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Operations.TranslateOperation import TranslateOperation
|
||||
from UM.Operations.ScaleToBoundsOperation import ScaleToBoundsOperation
|
||||
from UM.Math.Float import Float
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Application import Application
|
||||
|
||||
from . import PlatformPhysicsOperation
|
||||
from . import ConvexHullJob
|
||||
|
||||
import time
|
||||
import threading
|
||||
|
||||
class PlatformPhysics:
|
||||
def __init__(self, controller, volume):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
self._build_volume = volume
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(100)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self._onChangeTimerFinished)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
self._change_timer.start()
|
||||
|
||||
def _onChangeTimerFinished(self):
|
||||
root = self._controller.getScene().getRoot()
|
||||
for node in BreadthFirstIterator(root):
|
||||
if node is root or type(node) is not SceneNode:
|
||||
continue
|
||||
|
||||
bbox = node.getBoundingBox()
|
||||
if not bbox or not bbox.isValid():
|
||||
continue
|
||||
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
if self._build_volume.getBoundingBox().intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
node._outside_buildarea = True
|
||||
else:
|
||||
node._outside_buildarea = False
|
||||
|
||||
# Move the node upwards if the bottom is below the build platform.
|
||||
move_vector = Vector()
|
||||
if not Float.fuzzyCompare(bbox.bottom, 0.0):
|
||||
move_vector.setY(-bbox.bottom)
|
||||
|
||||
# If there is no convex hull for the node, start calculating it and continue.
|
||||
if not hasattr(node, "_convex_hull"):
|
||||
if not hasattr(node, "_convex_hull_job"):
|
||||
job = ConvexHullJob.ConvexHullJob(node)
|
||||
job.start()
|
||||
node._convex_hull_job = job
|
||||
else:
|
||||
# Check for collisions between convex hulls
|
||||
for other_node in BreadthFirstIterator(root):
|
||||
# Ignore root, ourselves and anything that is not a normal SceneNode.
|
||||
if other_node is root or type(other_node) is not SceneNode or other_node is node:
|
||||
continue
|
||||
|
||||
# Ignore nodes that do not have the right properties set.
|
||||
if not hasattr(other_node, "_convex_hull") or not other_node.getBoundingBox():
|
||||
continue
|
||||
|
||||
# Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects.
|
||||
if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
|
||||
continue
|
||||
|
||||
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
|
||||
overlap = node._convex_hull.intersectsPolygon(other_node._convex_hull)
|
||||
if overlap is None:
|
||||
continue
|
||||
|
||||
move_vector.setX(-overlap[0])
|
||||
move_vector.setZ(-overlap[1])
|
||||
|
||||
if move_vector != Vector():
|
||||
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
|
||||
op.push()
|
||||
|
||||
if node.getBoundingBox().intersectsBox(self._build_volume.getBoundingBox()) == AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
op = ScaleToBoundsOperation(node, self._build_volume.getBoundingBox())
|
||||
op.push()
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Operations.Operation import Operation
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Operations.TranslateOperation import TranslateOperation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
|
||||
## A specialised operation designed specifically to modify the previous operation.
|
||||
class PlatformPhysicsOperation(Operation):
|
||||
def __init__(self, node, translation):
|
||||
super().__init__()
|
||||
self._node = node
|
||||
self._old_position = node.getPosition()
|
||||
self._new_position = node.getPosition() + translation
|
||||
self._always_merge = True
|
||||
|
||||
def undo(self):
|
||||
self._node.setPosition(self._old_position)
|
||||
|
||||
def redo(self):
|
||||
self._node.setPosition(self._new_position)
|
||||
|
||||
def mergeWith(self, other):
|
||||
group = GroupedOperation()
|
||||
|
||||
group.addOperation(self)
|
||||
group.addOperation(other)
|
||||
|
||||
return group
|
||||
|
||||
def __repr__(self):
|
||||
return "PlatformPhysicsOperation(t = {0})".format(self._position)
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
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
|
||||
|
||||
## A class for processing and calculating minimum, currrent and maximum print time.
|
||||
#
|
||||
# This class contains all the logic relating to calculation and slicing for the
|
||||
# time/quality slider concept. It is a rather tricky combination of event handling
|
||||
# and state management. The logic behind this is as follows:
|
||||
#
|
||||
# - A scene change or settting change event happens.
|
||||
# We track what the source was of the change, either a scene change, a setting change, an active machine change or something else.
|
||||
# - This triggers a new slice with the current settings - this is the "current settings pass".
|
||||
# - When the slice is done, we update the current print time and material amount.
|
||||
# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
|
||||
# - When that is done, we update the minimum print time and start the final slcice pass, the "high quality settings pass".
|
||||
# - When the high quality pass is done, we update the maximum print time.
|
||||
#
|
||||
class PrintInformation(QObject):
|
||||
class SlicePass:
|
||||
CurrentSettings = 1
|
||||
LowQualitySettings = 2
|
||||
HighQualitySettings = 3
|
||||
|
||||
class SliceReason:
|
||||
SceneChanged = 1
|
||||
SettingChanged = 2
|
||||
ActiveMachineChanged = 3
|
||||
Other = 4
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._minimum_print_time = Duration(None, self)
|
||||
self._current_print_time = Duration(None, self)
|
||||
self._maximum_print_time = Duration(None, self)
|
||||
|
||||
self._material_amount = -1
|
||||
|
||||
self._time_quality_value = 50
|
||||
self._time_quality_changed_timer = QTimer()
|
||||
self._time_quality_changed_timer.setInterval(500)
|
||||
self._time_quality_changed_timer.setSingleShot(True)
|
||||
self._time_quality_changed_timer.timeout.connect(self._updateTimeQualitySettings)
|
||||
|
||||
self._interpolation_settings = {
|
||||
"layer_height": { "minimum": "low", "maximum": "high", "curve": "linear" },
|
||||
"fill_sparse_density": { "minimum": "low", "maximum": "high", "curve": "linear" }
|
||||
}
|
||||
|
||||
self._low_quality_settings = None
|
||||
self._current_settings = None
|
||||
self._high_quality_settings = None
|
||||
|
||||
self._slice_pass = None
|
||||
self._slice_reason = None
|
||||
|
||||
Application.getInstance().activeMachineChanged.connect(self._onActiveMachineChanged)
|
||||
self._onActiveMachineChanged()
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._backend = Application.getInstance().getBackend()
|
||||
if self._backend:
|
||||
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
|
||||
self._backend.slicingStarted.connect(self._onSlicingStarted)
|
||||
self._backend.slicingCancelled.connect(self._onSlicingCancelled)
|
||||
|
||||
minimumPrintTimeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(Duration, notify = minimumPrintTimeChanged)
|
||||
def minimumPrintTime(self):
|
||||
return self._minimum_print_time
|
||||
|
||||
currentPrintTimeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
|
||||
def currentPrintTime(self):
|
||||
return self._current_print_time
|
||||
|
||||
maximumPrintTimeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(Duration, notify = maximumPrintTimeChanged)
|
||||
def maximumPrintTime(self):
|
||||
return self._maximum_print_time
|
||||
|
||||
materialAmountChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(float, notify = materialAmountChanged)
|
||||
def materialAmount(self):
|
||||
return self._material_amount
|
||||
|
||||
timeQualityValueChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(int, notify = timeQualityValueChanged)
|
||||
def timeQualityValue(self):
|
||||
return self._time_quality_value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setTimeQualityValue(self, value):
|
||||
if value != self._time_quality_value:
|
||||
self._time_quality_value = value
|
||||
self.timeQualityValueChanged.emit()
|
||||
|
||||
self._time_quality_changed_timer.start()
|
||||
|
||||
def _onSlicingStarted(self):
|
||||
if self._slice_pass is None:
|
||||
self._slice_pass = self.SlicePass.CurrentSettings
|
||||
|
||||
if self._slice_reason is None:
|
||||
self._slice_reason = self.SliceReason.Other
|
||||
|
||||
if self._slice_pass == self.SlicePass.CurrentSettings and self._slice_reason != self.SliceReason.SettingChanged:
|
||||
self._minimum_print_time.setDuration(-1)
|
||||
self.minimumPrintTimeChanged.emit()
|
||||
self._maximum_print_time.setDuration(-1)
|
||||
self.maximumPrintTimeChanged.emit()
|
||||
|
||||
def _onPrintDurationMessage(self, time, amount):
|
||||
if self._slice_pass == self.SlicePass.CurrentSettings:
|
||||
self._current_print_time.setDuration(time)
|
||||
self.currentPrintTimeChanged.emit()
|
||||
|
||||
self._material_amount = round(amount / 10) / 100
|
||||
self.materialAmountChanged.emit()
|
||||
|
||||
if self._slice_reason != self.SliceReason.SettingChanged:
|
||||
self._slice_pass = self.SlicePass.LowQualitySettings
|
||||
self._backend.slice(settings = self._low_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False)
|
||||
else:
|
||||
self._slice_pass = None
|
||||
self._slice_reason = None
|
||||
elif self._slice_pass == self.SlicePass.LowQualitySettings:
|
||||
self._minimum_print_time.setDuration(time)
|
||||
self.minimumPrintTimeChanged.emit()
|
||||
|
||||
self._slice_pass = self.SlicePass.HighQualitySettings
|
||||
self._backend.slice(settings = self._high_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False)
|
||||
elif self._slice_pass == self.SlicePass.HighQualitySettings:
|
||||
self._maximum_print_time.setDuration(time)
|
||||
self.maximumPrintTimeChanged.emit()
|
||||
|
||||
self._slice_pass = None
|
||||
self._slice_reason = None
|
||||
|
||||
def _onActiveMachineChanged(self):
|
||||
if self._current_settings:
|
||||
self._current_settings.settingChanged.disconnect(self._onSettingChanged)
|
||||
|
||||
self._current_settings = Application.getInstance().getActiveMachine()
|
||||
|
||||
if self._current_settings:
|
||||
self._current_settings.settingChanged.connect(self._onSettingChanged)
|
||||
self._low_quality_settings = None
|
||||
self._high_quality_settings = None
|
||||
self._updateTimeQualitySettings()
|
||||
|
||||
self._slice_reason = self.SliceReason.ActiveMachineChanged
|
||||
|
||||
def _updateTimeQualitySettings(self):
|
||||
if not self._current_settings:
|
||||
return
|
||||
|
||||
if not self._low_quality_settings:
|
||||
self._low_quality_settings = MachineSettings()
|
||||
self._low_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + ".json"))
|
||||
self._low_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, "profiles", "low_quality.conf"))
|
||||
|
||||
if not self._high_quality_settings:
|
||||
self._high_quality_settings = MachineSettings()
|
||||
self._high_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + ".json"))
|
||||
self._high_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, "profiles", "high_quality.conf"))
|
||||
|
||||
for key, options in self._interpolation_settings.items():
|
||||
minimum_value = None
|
||||
if options["minimum"] == "low":
|
||||
minimum_value = self._low_quality_settings.getSettingValueByKey(key)
|
||||
elif options["minimum"] == "high":
|
||||
minimum_value = self._high_quality_settings.getSettingValueByKey(key)
|
||||
else:
|
||||
continue
|
||||
|
||||
maximum_value = None
|
||||
if options["maximum"] == "low":
|
||||
maximum_value = self._low_quality_settings.getSettingValueByKey(key)
|
||||
elif options["maximum"] == "high":
|
||||
maximum_value = self._high_quality_settings.getSettingValueByKey(key)
|
||||
else:
|
||||
continue
|
||||
|
||||
setting_value = minimum_value + (maximum_value - minimum_value) * (self._time_quality_value / 100)
|
||||
self._current_settings.setSettingValueByKey(key, setting_value)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
self._slice_reason = self.SliceReason.SceneChanged
|
||||
|
||||
def _onSettingChanged(self, source):
|
||||
self._slice_reason = self.SliceReason.SettingChanged
|
||||
|
||||
def _onSlicingCancelled(self):
|
||||
self._slice_pass = None
|
||||
Loading…
Add table
Add a link
Reference in a new issue