mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
CURA-4525 first multi slice + multi layer data, added filter on build plate, added option arrange on load, visuals like convex hull are now correct
This commit is contained in:
parent
41d5ec86a3
commit
e21acd1a07
18 changed files with 468 additions and 260 deletions
|
@ -40,7 +40,7 @@ class Arrange:
|
||||||
# \param fixed_nodes Scene nodes to be placed
|
# \param fixed_nodes Scene nodes to be placed
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
|
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
|
||||||
arranger = Arrange(x, y, x / 2, y / 2, scale = scale)
|
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
|
||||||
arranger.centerFirst()
|
arranger.centerFirst()
|
||||||
|
|
||||||
if fixed_nodes is None:
|
if fixed_nodes is None:
|
||||||
|
|
|
@ -112,7 +112,6 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
|
||||||
# start_priority = 0
|
# start_priority = 0
|
||||||
|
|
||||||
while try_placement:
|
while try_placement:
|
||||||
Logger.log("d", "start_priority %s", start_priority)
|
|
||||||
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
|
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
|
||||||
while current_build_plate_number >= arrange_array.count():
|
while current_build_plate_number >= arrange_array.count():
|
||||||
arrange_array.add()
|
arrange_array.add()
|
||||||
|
|
|
@ -6,7 +6,6 @@ from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Math.Color import Color
|
from UM.Math.Color import Color
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
|
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
|
||||||
|
|
||||||
from UM.View.GL.OpenGL import OpenGL
|
from UM.View.GL.OpenGL import OpenGL
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ class ConvexHullNode(SceneNode):
|
||||||
ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color)
|
ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color)
|
||||||
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
||||||
|
|
||||||
if self.getParent():
|
if self.getParent() and self.getParent().callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate:
|
||||||
if self.getMeshData():
|
if self.getMeshData():
|
||||||
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
|
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
|
||||||
if self._convex_hull_head_mesh:
|
if self._convex_hull_head_mesh:
|
||||||
|
|
|
@ -33,6 +33,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Operations.SetTransformOperation import SetTransformOperation
|
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||||
|
|
||||||
from cura.Arrange import Arrange
|
from cura.Arrange import Arrange
|
||||||
from cura.ShapeArray import ShapeArray
|
from cura.ShapeArray import ShapeArray
|
||||||
from cura.ConvexHullDecorator import ConvexHullDecorator
|
from cura.ConvexHullDecorator import ConvexHullDecorator
|
||||||
|
@ -41,6 +42,7 @@ from cura.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
from cura.BlockSlicingDecorator import BlockSlicingDecorator
|
from cura.BlockSlicingDecorator import BlockSlicingDecorator
|
||||||
# research
|
# research
|
||||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
|
||||||
from cura.ArrangeObjectsJob import ArrangeObjectsJob
|
from cura.ArrangeObjectsJob import ArrangeObjectsJob
|
||||||
from cura.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
|
from cura.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
|
||||||
|
@ -307,11 +309,13 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("cura/asked_dialog_on_project_save", False)
|
preferences.addPreference("cura/asked_dialog_on_project_save", False)
|
||||||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||||
|
preferences.addPreference("cura/arrange_objects_on_load", True)
|
||||||
|
|
||||||
preferences.addPreference("cura/currency", "€")
|
preferences.addPreference("cura/currency", "€")
|
||||||
preferences.addPreference("cura/material_settings", "{}")
|
preferences.addPreference("cura/material_settings", "{}")
|
||||||
|
|
||||||
preferences.addPreference("view/invert_zoom", False)
|
preferences.addPreference("view/invert_zoom", False)
|
||||||
|
preferences.addPreference("view/filter_current_build_plate", False)
|
||||||
|
|
||||||
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
|
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
|
||||||
|
|
||||||
|
@ -896,7 +900,7 @@ class CuraApplication(QtApplication):
|
||||||
scene_bounding_box = None
|
scene_bounding_box = None
|
||||||
is_block_slicing_node = False
|
is_block_slicing_node = False
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
||||||
continue
|
continue
|
||||||
if node.callDecoration("isBlockSlicing"):
|
if node.callDecoration("isBlockSlicing"):
|
||||||
is_block_slicing_node = True
|
is_block_slicing_node = True
|
||||||
|
@ -1013,7 +1017,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
Selection.clear()
|
Selection.clear()
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
@ -1021,6 +1025,9 @@ class CuraApplication(QtApplication):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
if not node.isSelectable():
|
if not node.isSelectable():
|
||||||
continue # i.e. node with layer data
|
continue # i.e. node with layer data
|
||||||
|
if not node.callDecoration("isSliceable"):
|
||||||
|
continue # i.e. node with layer data
|
||||||
|
|
||||||
Selection.add(node)
|
Selection.add(node)
|
||||||
|
|
||||||
## Delete all nodes containing mesh data in the scene.
|
## Delete all nodes containing mesh data in the scene.
|
||||||
|
@ -1032,7 +1039,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
|
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
@ -1054,7 +1061,7 @@ class CuraApplication(QtApplication):
|
||||||
Logger.log("i", "Resetting all scene translations")
|
Logger.log("i", "Resetting all scene translations")
|
||||||
nodes = []
|
nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
@ -1082,13 +1089,13 @@ class CuraApplication(QtApplication):
|
||||||
Logger.log("i", "Resetting all scene transformations")
|
Logger.log("i", "Resetting all scene transformations")
|
||||||
nodes = []
|
nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
if not node.isSelectable():
|
if not node.callDecoration("isSliceable"):
|
||||||
continue # i.e. node with layer data
|
continue # i.e. node with layer data
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
|
||||||
|
@ -1109,7 +1116,27 @@ class CuraApplication(QtApplication):
|
||||||
def arrangeObjectsToAllBuildPlates(self):
|
def arrangeObjectsToAllBuildPlates(self):
|
||||||
nodes = []
|
nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
|
continue
|
||||||
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.callDecoration("isSliceable"):
|
||||||
|
continue # i.e. node with layer data
|
||||||
|
# Skip nodes that are too big
|
||||||
|
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||||
|
nodes.append(node)
|
||||||
|
job = ArrangeObjectsAllBuildPlatesJob(nodes)
|
||||||
|
job.start()
|
||||||
|
self.setActiveBuildPlate(0)
|
||||||
|
|
||||||
|
# Single build plate
|
||||||
|
@pyqtSlot()
|
||||||
|
def arrangeAll(self):
|
||||||
|
nodes = []
|
||||||
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
@ -1117,24 +1144,7 @@ class CuraApplication(QtApplication):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
if not node.isSelectable():
|
if not node.isSelectable():
|
||||||
continue # i.e. node with layer data
|
continue # i.e. node with layer data
|
||||||
# Skip nodes that are too big
|
if not node.callDecoration("isSliceable"):
|
||||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
|
||||||
nodes.append(node)
|
|
||||||
job = ArrangeObjectsAllBuildPlatesJob(nodes)
|
|
||||||
job.start()
|
|
||||||
|
|
||||||
# Single build plate
|
|
||||||
@pyqtSlot()
|
|
||||||
def arrangeAll(self):
|
|
||||||
nodes = []
|
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
|
||||||
if type(node) is not SceneNode:
|
|
||||||
continue
|
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
|
||||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
|
||||||
if not node.isSelectable():
|
|
||||||
continue # i.e. node with layer data
|
continue # i.e. node with layer data
|
||||||
if node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
|
if node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
|
||||||
# Skip nodes that are too big
|
# Skip nodes that are too big
|
||||||
|
@ -1150,7 +1160,7 @@ class CuraApplication(QtApplication):
|
||||||
# What nodes are on the build plate and are not being moved
|
# What nodes are on the build plate and are not being moved
|
||||||
fixed_nodes = []
|
fixed_nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode:
|
if not issubclass(type(node), SceneNode):
|
||||||
continue
|
continue
|
||||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
|
@ -1158,6 +1168,8 @@ class CuraApplication(QtApplication):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
if not node.isSelectable():
|
if not node.isSelectable():
|
||||||
continue # i.e. node with layer data
|
continue # i.e. node with layer data
|
||||||
|
if not node.callDecoration("isSliceable"):
|
||||||
|
continue # i.e. node with layer data
|
||||||
if node in nodes: # exclude selected node from fixed_nodes
|
if node in nodes: # exclude selected node from fixed_nodes
|
||||||
continue
|
continue
|
||||||
fixed_nodes.append(node)
|
fixed_nodes.append(node)
|
||||||
|
@ -1176,7 +1188,7 @@ class CuraApplication(QtApplication):
|
||||||
Logger.log("i", "Reloading all loaded mesh data.")
|
Logger.log("i", "Reloading all loaded mesh data.")
|
||||||
nodes = []
|
nodes = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode or not node.getMeshData():
|
if not issubclass(type(node), SceneNode) or not node.getMeshData():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
@ -1267,7 +1279,7 @@ class CuraApplication(QtApplication):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def groupSelected(self):
|
def groupSelected(self):
|
||||||
# Create a group-node
|
# Create a group-node
|
||||||
group_node = SceneNode()
|
group_node = CuraSceneNode()
|
||||||
group_decorator = GroupDecorator()
|
group_decorator = GroupDecorator()
|
||||||
group_node.addDecorator(group_decorator)
|
group_node.addDecorator(group_decorator)
|
||||||
group_node.addDecorator(ConvexHullDecorator())
|
group_node.addDecorator(ConvexHullDecorator())
|
||||||
|
@ -1413,11 +1425,15 @@ class CuraApplication(QtApplication):
|
||||||
min_offset = 8
|
min_offset = 8
|
||||||
|
|
||||||
self.fileLoaded.emit(filename)
|
self.fileLoaded.emit(filename)
|
||||||
|
arrange_objects_on_load = Preferences.getInstance().getValue("cura/arrange_objects_on_load")
|
||||||
|
target_build_plate = self.activeBuildPlate if arrange_objects_on_load else -1
|
||||||
|
|
||||||
|
for original_node in nodes:
|
||||||
|
node = CuraSceneNode() # We want our own CuraSceneNode
|
||||||
|
node.setMeshData(original_node.getMeshData())
|
||||||
|
|
||||||
for node in nodes:
|
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
node.setName(os.path.basename(filename))
|
node.setName(os.path.basename(filename))
|
||||||
node.addDecorator(BuildPlateDecorator())
|
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1]
|
extension = os.path.splitext(filename)[1]
|
||||||
if extension.lower() in self._non_sliceable_extensions:
|
if extension.lower() in self._non_sliceable_extensions:
|
||||||
|
@ -1442,20 +1458,23 @@ class CuraApplication(QtApplication):
|
||||||
if not child.getDecorator(ConvexHullDecorator):
|
if not child.getDecorator(ConvexHullDecorator):
|
||||||
child.addDecorator(ConvexHullDecorator())
|
child.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
if node.callDecoration("isSliceable"):
|
if arrange_objects_on_load:
|
||||||
# Only check position if it's not already blatantly obvious that it won't fit.
|
if node.callDecoration("isSliceable"):
|
||||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
# Only check position if it's not already blatantly obvious that it won't fit.
|
||||||
# Find node location
|
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||||
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
|
# Find node location
|
||||||
|
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
|
||||||
|
|
||||||
# If a model is to small then it will not contain any points
|
# If a model is to small then it will not contain any points
|
||||||
if offset_shape_arr is None and hull_shape_arr is None:
|
if offset_shape_arr is None and hull_shape_arr is None:
|
||||||
Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
|
Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
|
||||||
title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
|
title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
||||||
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
||||||
|
|
||||||
|
node.addDecorator(BuildPlateDecorator(target_build_plate))
|
||||||
|
|
||||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
op = AddSceneNodeOperation(node, scene.getRoot())
|
||||||
op.push()
|
op.push()
|
||||||
|
@ -1494,6 +1513,8 @@ class CuraApplication(QtApplication):
|
||||||
#### research - hacky place for these kind of thing
|
#### research - hacky place for these kind of thing
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def setActiveBuildPlate(self, nr):
|
def setActiveBuildPlate(self, nr):
|
||||||
|
if nr == self._active_build_plate:
|
||||||
|
return
|
||||||
Logger.log("d", "Select build plate: %s" % nr)
|
Logger.log("d", "Select build plate: %s" % nr)
|
||||||
self._active_build_plate = nr
|
self._active_build_plate = nr
|
||||||
|
|
||||||
|
|
|
@ -7,23 +7,35 @@ from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
#from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
from UM.Preferences import Preferences
|
||||||
|
|
||||||
|
|
||||||
class ObjectManager(ListModel):
|
class ObjectManager(ListModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._last_selected_index = 0
|
self._last_selected_index = 0
|
||||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
|
Application.getInstance().getController().getScene().sceneChanged.connect(self._update_scene_changed)
|
||||||
|
Preferences.getInstance().preferenceChanged.connect(self._update)
|
||||||
|
Application.getInstance().activeBuildPlateChanged.connect(self._update)
|
||||||
|
|
||||||
def _update(self, *args):
|
def _update(self, *args):
|
||||||
nodes = []
|
nodes = []
|
||||||
|
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
|
||||||
|
active_build_plate_number = Application.getInstance().activeBuildPlate
|
||||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||||
if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
||||||
|
continue
|
||||||
|
if not node.callDecoration("isSliceable"):
|
||||||
|
continue
|
||||||
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
|
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
|
||||||
continue
|
continue
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"name": node.getName(),
|
"name": node.getName(),
|
||||||
"isSelected": Selection.isSelected(node),
|
"isSelected": Selection.isSelected(node),
|
||||||
"buildPlateNumber": node.callDecoration("getBuildPlateNumber"),
|
"isOutsideBuildArea": node.isOutsideBuildArea(),
|
||||||
|
"buildPlateNumber": node_build_plate_number,
|
||||||
"node": node
|
"node": node
|
||||||
})
|
})
|
||||||
nodes = sorted(nodes, key=lambda n: n["name"])
|
nodes = sorted(nodes, key=lambda n: n["name"])
|
||||||
|
@ -31,6 +43,12 @@ class ObjectManager(ListModel):
|
||||||
|
|
||||||
self.itemsChanged.emit()
|
self.itemsChanged.emit()
|
||||||
|
|
||||||
|
def _update_scene_changed(self, *args):
|
||||||
|
# if args and type(args[0]) is not CuraSceneNode:
|
||||||
|
# Logger.log("d", " ascdf %s", args)
|
||||||
|
# return
|
||||||
|
self._update(*args)
|
||||||
|
|
||||||
## Either select or deselect an item
|
## Either select or deselect an item
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def changeSelection(self, index):
|
def changeSelection(self, index):
|
||||||
|
@ -63,6 +81,11 @@ class ObjectManager(ListModel):
|
||||||
|
|
||||||
self._last_selected_index = index
|
self._last_selected_index = index
|
||||||
|
|
||||||
|
# testing
|
||||||
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||||
|
if node.callDecoration("getLayerData"):
|
||||||
|
Logger.log("d", " ##### NODE: %s", node)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def createObjectManager():
|
def createObjectManager():
|
||||||
return ObjectManager()
|
return ObjectManager()
|
||||||
|
|
|
@ -6,11 +6,14 @@ from UM.Logger import Logger
|
||||||
class BuildPlateDecorator(SceneNodeDecorator):
|
class BuildPlateDecorator(SceneNodeDecorator):
|
||||||
def __init__(self, build_plate_number = -1):
|
def __init__(self, build_plate_number = -1):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._build_plate_number = None
|
||||||
|
self._previous_build_plate_number = None
|
||||||
self.setBuildPlateNumber(build_plate_number)
|
self.setBuildPlateNumber(build_plate_number)
|
||||||
|
|
||||||
def setBuildPlateNumber(self, nr):
|
def setBuildPlateNumber(self, nr):
|
||||||
# Make sure that groups are set correctly
|
# Make sure that groups are set correctly
|
||||||
# setBuildPlateForSelection in CuraActions makes sure that no single childs are set.
|
# setBuildPlateForSelection in CuraActions makes sure that no single childs are set.
|
||||||
|
self._previous_build_plate_number = self._build_plate_number
|
||||||
self._build_plate_number = nr
|
self._build_plate_number = nr
|
||||||
if self._node and self._node.callDecoration("isGroup"):
|
if self._node and self._node.callDecoration("isGroup"):
|
||||||
for child in self._node.getChildren():
|
for child in self._node.getChildren():
|
||||||
|
@ -19,5 +22,9 @@ class BuildPlateDecorator(SceneNodeDecorator):
|
||||||
def getBuildPlateNumber(self):
|
def getBuildPlateNumber(self):
|
||||||
return self._build_plate_number
|
return self._build_plate_number
|
||||||
|
|
||||||
|
# Used to determine from what build plate the node moved.
|
||||||
|
def getPreviousBuildPlateNumber(self):
|
||||||
|
return self._previous_build_plate_number
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
return BuildPlateDecorator()
|
return BuildPlateDecorator()
|
||||||
|
|
40
cura/Scene/CuraSceneNode.py
Normal file
40
cura/Scene/CuraSceneNode.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||||
|
# Note that many other nodes can just be UM SceneNode objects.
|
||||||
|
class CuraSceneNode(SceneNode):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._outside_buildarea = True
|
||||||
|
|
||||||
|
def setOutsideBuildArea(self, new_value):
|
||||||
|
self._outside_buildarea = new_value
|
||||||
|
|
||||||
|
def isOutsideBuildArea(self):
|
||||||
|
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
||||||
|
|
||||||
|
def isVisible(self):
|
||||||
|
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate
|
||||||
|
|
||||||
|
def isSelectable(self) -> bool:
|
||||||
|
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate
|
||||||
|
|
||||||
|
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
copy = CuraSceneNode()
|
||||||
|
copy.setTransformation(self.getLocalTransformation())
|
||||||
|
copy.setMeshData(self._mesh_data)
|
||||||
|
copy.setVisible(deepcopy(self._visible, memo))
|
||||||
|
copy._selectable = deepcopy(self._selectable, memo)
|
||||||
|
copy._name = deepcopy(self._name, memo)
|
||||||
|
for decorator in self._decorators:
|
||||||
|
copy.addDecorator(deepcopy(decorator, memo))
|
||||||
|
|
||||||
|
for child in self._children:
|
||||||
|
copy.addChild(deepcopy(child, memo))
|
||||||
|
self.calculateBoundingBoxMesh()
|
||||||
|
return copy
|
|
@ -15,7 +15,8 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.QualityManager import QualityManager
|
from cura.QualityManager import QualityManager
|
||||||
from UM.Scene.SceneNode import SceneNode
|
#from UM.Scene.SceneNode import SceneNode
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||||
from cura.SliceableObjectDecorator import SliceableObjectDecorator
|
from cura.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
from cura.ZOffsetDecorator import ZOffsetDecorator
|
from cura.ZOffsetDecorator import ZOffsetDecorator
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# Workaround to disable layer view processing if layer view is not active.
|
# Workaround to disable layer view processing if layer view is not active.
|
||||||
self._layer_view_active = False
|
self._layer_view_active = False
|
||||||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||||
|
Application.getInstance().activeBuildPlateChanged.connect(self._onActiveViewChanged)
|
||||||
self._onActiveViewChanged()
|
self._onActiveViewChanged()
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
self._stored_optimized_layer_data = []
|
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||||
|
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||||
|
@ -104,12 +105,14 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
||||||
|
|
||||||
self._start_slice_job = None
|
self._start_slice_job = None
|
||||||
|
self._start_slice_job_build_plate = None
|
||||||
self._slicing = False # Are we currently slicing?
|
self._slicing = False # Are we currently slicing?
|
||||||
self._restart = False # Back-end is currently restarting?
|
self._restart = False # Back-end is currently restarting?
|
||||||
self._tool_active = False # If a tool is active, some tasks do not have to do anything
|
self._tool_active = False # If a tool is active, some tasks do not have to do anything
|
||||||
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||||
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
|
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
|
||||||
self._need_slicing = False
|
# self._need_slicing = False
|
||||||
|
self._build_plates_to_be_sliced = [] # what needs slicing?
|
||||||
self._engine_is_fresh = True # Is the newly started engine used before or not?
|
self._engine_is_fresh = True # Is the newly started engine used before or not?
|
||||||
|
|
||||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
||||||
|
@ -189,8 +192,9 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Perform a slice of the scene.
|
## Perform a slice of the scene.
|
||||||
def slice(self):
|
def slice(self):
|
||||||
|
Logger.log("d", "starting to slice again!")
|
||||||
self._slice_start_time = time()
|
self._slice_start_time = time()
|
||||||
if not self._need_slicing:
|
if not self._build_plates_to_be_sliced:
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
self.backendStateChange.emit(BackendState.Done)
|
self.backendStateChange.emit(BackendState.Done)
|
||||||
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
|
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
|
||||||
|
@ -199,7 +203,6 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Application.getInstance().getPrintInformation().setToZeroPrintInformation()
|
Application.getInstance().getPrintInformation().setToZeroPrintInformation()
|
||||||
|
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
self._stored_optimized_layer_data = []
|
|
||||||
|
|
||||||
if self._process is None:
|
if self._process is None:
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
@ -215,6 +218,9 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
slice_message = self._socket.createMessage("cura.proto.Slice")
|
slice_message = self._socket.createMessage("cura.proto.Slice")
|
||||||
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
|
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
|
||||||
|
self._start_slice_job_build_plate = self._build_plates_to_be_sliced.pop(0)
|
||||||
|
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
|
||||||
|
self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
|
||||||
self._start_slice_job.start()
|
self._start_slice_job.start()
|
||||||
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
|
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
|
||||||
|
|
||||||
|
@ -223,7 +229,8 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
def _terminate(self):
|
def _terminate(self):
|
||||||
self._slicing = False
|
self._slicing = False
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
self._stored_optimized_layer_data = []
|
if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
|
||||||
|
del self._stored_optimized_layer_data[self._start_slice_job_build_plate]
|
||||||
if self._start_slice_job is not None:
|
if self._start_slice_job is not None:
|
||||||
self._start_slice_job.cancel()
|
self._start_slice_job.cancel()
|
||||||
|
|
||||||
|
@ -315,10 +322,13 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
#self.backendStateChange.emit(BackendState.Error)
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
#self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
pass
|
||||||
|
self._invokeSlice()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Preparation completed, send it to the backend.
|
# Preparation completed, send it to the backend.
|
||||||
self._socket.sendMessage(job.getSliceMessage())
|
self._socket.sendMessage(job.getSliceMessage())
|
||||||
|
|
||||||
|
@ -360,27 +370,34 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
# \param source The scene node that was changed.
|
# \param source The scene node that was changed.
|
||||||
def _onSceneChanged(self, source):
|
def _onSceneChanged(self, source):
|
||||||
if type(source) is not SceneNode:
|
Logger.log("d", " ##### scene changed: %s", source)
|
||||||
|
if not issubclass(type(source), SceneNode):
|
||||||
return
|
return
|
||||||
|
|
||||||
root_scene_nodes_changed = False
|
root_scene_nodes_changed = False
|
||||||
|
build_plates_changed = set()
|
||||||
if source == self._scene.getRoot():
|
if source == self._scene.getRoot():
|
||||||
num_objects = 0
|
num_objects = 0
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
# Only count sliceable objects
|
# Only count sliceable objects
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
num_objects += 1
|
num_objects += 1
|
||||||
|
build_plates_changed.add(node.callDecoration("getBuildPlateNumber"))
|
||||||
|
build_plates_changed.add(node.callDecoration("getPreviousBuildPlateNumber"))
|
||||||
if num_objects != self._last_num_objects:
|
if num_objects != self._last_num_objects:
|
||||||
self._last_num_objects = num_objects
|
self._last_num_objects = num_objects
|
||||||
root_scene_nodes_changed = True
|
root_scene_nodes_changed = True
|
||||||
else:
|
# else:
|
||||||
return
|
# return # ??
|
||||||
|
build_plates_changed.discard(None)
|
||||||
|
build_plates_changed.discard(-1) # object not on build plate
|
||||||
|
Logger.log("d", " #### build plates changed: %s", build_plates_changed)
|
||||||
|
|
||||||
if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
|
# if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
|
||||||
if source.getMeshData() is None:
|
# if source.getMeshData() is None:
|
||||||
return
|
# return
|
||||||
if source.getMeshData().getVertices() is None:
|
# if source.getMeshData().getVertices() is None:
|
||||||
return
|
# return
|
||||||
|
|
||||||
if self._tool_active:
|
if self._tool_active:
|
||||||
# do it later, each source only has to be done once
|
# do it later, each source only has to be done once
|
||||||
|
@ -388,9 +405,24 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._postponed_scene_change_sources.append(source)
|
self._postponed_scene_change_sources.append(source)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.needsSlicing()
|
if build_plates_changed:
|
||||||
self.stopSlicing()
|
Logger.log("d", " going to reslice")
|
||||||
self._onChanged()
|
self.stopSlicing()
|
||||||
|
for build_plate_number in build_plates_changed:
|
||||||
|
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||||
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
|
self.processingProgress.emit(0.0)
|
||||||
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
if not self._use_timer:
|
||||||
|
# With manually having to slice, we want to clear the old invalid layer data.
|
||||||
|
self._clearLayerData(build_plates_changed)
|
||||||
|
|
||||||
|
self._invokeSlice()
|
||||||
|
|
||||||
|
# #self.needsSlicing()
|
||||||
|
# self.stopSlicing()
|
||||||
|
# #self._onChanged()
|
||||||
|
# self._invokeSlice()
|
||||||
|
|
||||||
## Called when an error occurs in the socket connection towards the engine.
|
## Called when an error occurs in the socket connection towards the engine.
|
||||||
#
|
#
|
||||||
|
@ -410,16 +442,24 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Logger.log("w", "A socket error caused the connection to be reset")
|
Logger.log("w", "A socket error caused the connection to be reset")
|
||||||
|
|
||||||
## Remove old layer data (if any)
|
## Remove old layer data (if any)
|
||||||
def _clearLayerData(self):
|
def _clearLayerData(self, build_plate_numbers = set()):
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if node.callDecoration("getLayerData"):
|
if node.callDecoration("getLayerData"):
|
||||||
node.getParent().removeChild(node)
|
if node.callDecoration("getBuildPlateNumber") in build_plate_numbers or not build_plate_numbers:
|
||||||
break
|
node.getParent().removeChild(node)
|
||||||
|
|
||||||
## Convenient function: set need_slicing, emit state and clear layer data
|
def markSliceAll(self):
|
||||||
|
if 0 not in self._build_plates_to_be_sliced:
|
||||||
|
self._build_plates_to_be_sliced.append(0)
|
||||||
|
if 1 not in self._build_plates_to_be_sliced:
|
||||||
|
self._build_plates_to_be_sliced.append(1)
|
||||||
|
if 2 not in self._build_plates_to_be_sliced:
|
||||||
|
self._build_plates_to_be_sliced.append(2)
|
||||||
|
|
||||||
|
## Convenient function: mark everything to slice, emit state and clear layer data
|
||||||
def needsSlicing(self):
|
def needsSlicing(self):
|
||||||
self.stopSlicing()
|
self.stopSlicing()
|
||||||
self._need_slicing = True
|
self.markSliceAll()
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
if not self._use_timer:
|
if not self._use_timer:
|
||||||
|
@ -441,7 +481,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
def _onStackErrorCheckFinished(self):
|
def _onStackErrorCheckFinished(self):
|
||||||
self._is_error_check_scheduled = False
|
self._is_error_check_scheduled = False
|
||||||
if not self._slicing and self._need_slicing:
|
if not self._slicing and self._build_plates_to_be_sliced: #self._need_slicing:
|
||||||
self.needsSlicing()
|
self.needsSlicing()
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
|
@ -455,7 +495,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing sliced layer data.
|
# \param message The protobuf message containing sliced layer data.
|
||||||
def _onOptimizedLayerMessage(self, message):
|
def _onOptimizedLayerMessage(self, message):
|
||||||
self._stored_optimized_layer_data.append(message)
|
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
|
||||||
|
|
||||||
## Called when a progress message is received from the engine.
|
## Called when a progress message is received from the engine.
|
||||||
#
|
#
|
||||||
|
@ -464,6 +504,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.processingProgress.emit(message.amount)
|
self.processingProgress.emit(message.amount)
|
||||||
self.backendStateChange.emit(BackendState.Processing)
|
self.backendStateChange.emit(BackendState.Processing)
|
||||||
|
|
||||||
|
# testing
|
||||||
|
def _invokeSlice(self):
|
||||||
|
if self._use_timer:
|
||||||
|
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
||||||
|
# otherwise business as usual
|
||||||
|
if self._is_error_check_scheduled:
|
||||||
|
self._change_timer.stop()
|
||||||
|
else:
|
||||||
|
self._change_timer.start()
|
||||||
|
|
||||||
## Called when the engine sends a message that slicing is finished.
|
## Called when the engine sends a message that slicing is finished.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message signalling that slicing is finished.
|
# \param message The protobuf message signalling that slicing is finished.
|
||||||
|
@ -481,13 +531,20 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
|
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
|
||||||
|
|
||||||
self._slicing = False
|
self._slicing = False
|
||||||
self._need_slicing = False
|
#self._need_slicing = False
|
||||||
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
||||||
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
|
|
||||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
|
# See if we need to process the sliced layers job.
|
||||||
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
active_build_plate = Application.getInstance().activeBuildPlate
|
||||||
self._process_layers_job.start()
|
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
|
||||||
self._stored_optimized_layer_data = []
|
self._startProcessSlicedLayersJob(active_build_plate)
|
||||||
|
self._start_slice_job_build_plate = None
|
||||||
|
|
||||||
|
Logger.log("d", "See if there is more to slice...")
|
||||||
|
# Somehow this results in an Arcus Error
|
||||||
|
# self.slice()
|
||||||
|
# Testing call slice again, allow backend to restart by using the timer
|
||||||
|
self._invokeSlice()
|
||||||
|
|
||||||
## Called when a g-code message is received from the engine.
|
## Called when a g-code message is received from the engine.
|
||||||
#
|
#
|
||||||
|
@ -584,19 +641,26 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
source = self._postponed_scene_change_sources.pop(0)
|
source = self._postponed_scene_change_sources.pop(0)
|
||||||
self._onSceneChanged(source)
|
self._onSceneChanged(source)
|
||||||
|
|
||||||
|
def _startProcessSlicedLayersJob(self, build_plate_number):
|
||||||
|
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
|
||||||
|
self._process_layers_job.setBuildPlate(build_plate_number)
|
||||||
|
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
||||||
|
self._process_layers_job.start()
|
||||||
|
del self._stored_optimized_layer_data[build_plate_number]
|
||||||
|
|
||||||
## Called when the user changes the active view mode.
|
## Called when the user changes the active view mode.
|
||||||
def _onActiveViewChanged(self):
|
def _onActiveViewChanged(self):
|
||||||
if Application.getInstance().getController().getActiveView():
|
application = Application.getInstance()
|
||||||
view = Application.getInstance().getController().getActiveView()
|
view = application.getController().getActiveView()
|
||||||
|
if view:
|
||||||
|
active_build_plate = application.activeBuildPlate
|
||||||
if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||||
self._layer_view_active = True
|
self._layer_view_active = True
|
||||||
# There is data and we're not slicing at the moment
|
# 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 we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
||||||
if self._stored_optimized_layer_data and not self._slicing:
|
# TODO: what build plate I am slicing
|
||||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
|
if active_build_plate in self._stored_optimized_layer_data and not self._slicing:
|
||||||
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
self._startProcessSlicedLayersJob(active_build_plate)
|
||||||
self._process_layers_job.start()
|
|
||||||
self._stored_optimized_layer_data = []
|
|
||||||
else:
|
else:
|
||||||
self._layer_view_active = False
|
self._layer_view_active = False
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,12 @@ from UM.Logger import Logger
|
||||||
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
|
||||||
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura import LayerDataBuilder
|
from cura import LayerDataBuilder
|
||||||
from cura import LayerDataDecorator
|
from cura import LayerDataDecorator
|
||||||
from cura import LayerPolygon
|
from cura import LayerPolygon
|
||||||
|
# from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from time import time
|
from time import time
|
||||||
|
@ -49,6 +51,7 @@ class ProcessSlicedLayersJob(Job):
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
|
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
|
||||||
self._abort_requested = False
|
self._abort_requested = False
|
||||||
|
self._build_plate_number = None
|
||||||
|
|
||||||
## Aborts the processing of layers.
|
## Aborts the processing of layers.
|
||||||
#
|
#
|
||||||
|
@ -59,7 +62,11 @@ class ProcessSlicedLayersJob(Job):
|
||||||
def abort(self):
|
def abort(self):
|
||||||
self._abort_requested = True
|
self._abort_requested = True
|
||||||
|
|
||||||
|
def setBuildPlate(self, new_value):
|
||||||
|
self._build_plate_number = new_value
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
Logger.log("d", "########## Processing new layer for [%s]..." % self._build_plate_number)
|
||||||
start_time = time()
|
start_time = time()
|
||||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||||
self._progress_message.show()
|
self._progress_message.show()
|
||||||
|
@ -72,16 +79,18 @@ class ProcessSlicedLayersJob(Job):
|
||||||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||||
|
|
||||||
new_node = SceneNode()
|
new_node = SceneNode()
|
||||||
|
new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
|
||||||
|
|
||||||
## Remove old layer data (if any)
|
# ## Remove old layer data (if any)
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
# for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if node.callDecoration("getLayerData"):
|
# if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
||||||
node.getParent().removeChild(node)
|
# Logger.log("d", " # Removing: %s", node)
|
||||||
break
|
# node.getParent().removeChild(node)
|
||||||
if self._abort_requested:
|
# #break
|
||||||
if self._progress_message:
|
# if self._abort_requested:
|
||||||
self._progress_message.hide()
|
# if self._progress_message:
|
||||||
return
|
# self._progress_message.hide()
|
||||||
|
# return
|
||||||
|
|
||||||
# Force garbage collection.
|
# Force garbage collection.
|
||||||
# For some reason, Python has a tendency to keep the layer data
|
# For some reason, Python has a tendency to keep the layer data
|
||||||
|
|
|
@ -10,12 +10,13 @@ from UM.Job import Job
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
from UM.Scene.SceneNode import SceneNode
|
#from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Settings.Validator import ValidatorState
|
||||||
from UM.Settings.SettingRelation import RelationType
|
from UM.Settings.SettingRelation import RelationType
|
||||||
|
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
@ -58,10 +59,14 @@ class StartSliceJob(Job):
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
self._slice_message = slice_message
|
self._slice_message = slice_message
|
||||||
self._is_cancelled = False
|
self._is_cancelled = False
|
||||||
|
self._build_plate_number = None
|
||||||
|
|
||||||
def getSliceMessage(self):
|
def getSliceMessage(self):
|
||||||
return self._slice_message
|
return self._slice_message
|
||||||
|
|
||||||
|
def setBuildPlate(self, build_plate_number):
|
||||||
|
self._build_plate_number = build_plate_number
|
||||||
|
|
||||||
## Check if a stack has any errors.
|
## Check if a stack has any errors.
|
||||||
## returns true if it has errors, false otherwise.
|
## returns true if it has errors, false otherwise.
|
||||||
def _checkStackForErrors(self, stack):
|
def _checkStackForErrors(self, stack):
|
||||||
|
@ -78,6 +83,10 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
## Runs the job that initiates the slicing.
|
## Runs the job that initiates the slicing.
|
||||||
def run(self):
|
def run(self):
|
||||||
|
if self._build_plate_number is None:
|
||||||
|
self.setResult(StartJobResult.Error)
|
||||||
|
return
|
||||||
|
|
||||||
stack = Application.getInstance().getGlobalContainerStack()
|
stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if not stack:
|
if not stack:
|
||||||
self.setResult(StartJobResult.Error)
|
self.setResult(StartJobResult.Error)
|
||||||
|
@ -141,14 +150,12 @@ class StartSliceJob(Job):
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||||
|
|
||||||
# temp hack to filter on build plate 0
|
if (node.callDecoration("getBuildPlateNumber") == self._build_plate_number):
|
||||||
if (node.callDecoration("getBuildPlateNumber") == 0):
|
if not getattr(node, "_outside_buildarea", False) or (node.callDecoration("getStack") and any(node.callDecoration("getStack").getProperty(setting, "value") for setting in self._not_printed_mesh_settings)):
|
||||||
|
|
||||||
if not getattr(node, "_outside_buildarea", False)\
|
|
||||||
or (node.callDecoration("getStack") and any(node.callDecoration("getStack").getProperty(setting, "value") for setting in self._not_printed_mesh_settings)):
|
|
||||||
temp_list.append(node)
|
temp_list.append(node)
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
|
Logger.log("d", " objects to be sliced: %s", temp_list)
|
||||||
if temp_list:
|
if temp_list:
|
||||||
object_groups.append(temp_list)
|
object_groups.append(temp_list)
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,14 @@ from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from UM.Mesh.MeshReader import MeshReader
|
from UM.Mesh.MeshReader import MeshReader
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Scene.SceneNode import SceneNode
|
#from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from .ImageReaderUI import ImageReaderUI
|
from .ImageReaderUI import ImageReaderUI
|
||||||
|
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||||
|
|
||||||
|
|
||||||
class ImageReader(MeshReader):
|
class ImageReader(MeshReader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -66,13 +66,14 @@ class LayerPass(RenderPass):
|
||||||
self.bind()
|
self.bind()
|
||||||
|
|
||||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
|
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
|
||||||
|
active_build_plate = Application.getInstance().activeBuildPlate
|
||||||
|
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
|
|
||||||
if isinstance(node, ToolHandle):
|
if isinstance(node, ToolHandle):
|
||||||
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
||||||
|
|
||||||
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
|
elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible() and node.callDecoration("getBuildPlateNumber") == active_build_plate:
|
||||||
layer_data = node.callDecoration("getLayerData")
|
layer_data = node.callDecoration("getLayerData")
|
||||||
if not layer_data:
|
if not layer_data:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -75,7 +75,7 @@ class SolidView(View):
|
||||||
|
|
||||||
for node in DepthFirstIterator(scene.getRoot()):
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
if not node.render(renderer):
|
if not node.render(renderer):
|
||||||
if node.getMeshData() and node.isVisible() and (node.callDecoration("getBuildPlateNumber") == activeBuildPlateNumber):
|
if node.getMeshData() and node.isVisible(): # and (node.callDecoration("getBuildPlateNumber") == activeBuildPlateNumber):
|
||||||
uniforms = {}
|
uniforms = {}
|
||||||
shade_factor = 1.0
|
shade_factor = 1.0
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Mesh.MeshReader import MeshReader
|
from UM.Mesh.MeshReader import MeshReader
|
||||||
from UM.Scene.SceneNode import SceneNode
|
#from UM.Scene.SceneNode import SceneNode
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
try:
|
try:
|
||||||
|
@ -19,63 +20,63 @@ try:
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
# TODO: preserve the structure of scenes that contain several objects
|
# TODO: preserve the structure of scenes that contain several objects
|
||||||
# Use CADPart, for example, to distinguish between separate objects
|
# Use CADPart, for example, to distinguish between separate objects
|
||||||
|
|
||||||
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
|
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
|
||||||
EPSILON = 0.000001
|
EPSILON = 0.000001
|
||||||
|
|
||||||
class Shape:
|
class Shape:
|
||||||
|
|
||||||
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
|
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
|
||||||
# with vertices stored in rows
|
# with vertices stored in rows
|
||||||
def __init__(self, verts, faces, index_base, name):
|
def __init__(self, verts, faces, index_base, name):
|
||||||
self.verts = verts
|
self.verts = verts
|
||||||
self.faces = faces
|
self.faces = faces
|
||||||
# Those are here for debugging purposes only
|
# Those are here for debugging purposes only
|
||||||
self.index_base = index_base
|
self.index_base = index_base
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
class X3DReader(MeshReader):
|
class X3DReader(MeshReader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._supported_extensions = [".x3d"]
|
self._supported_extensions = [".x3d"]
|
||||||
self._namespaces = {}
|
self._namespaces = {}
|
||||||
|
|
||||||
# Main entry point
|
# Main entry point
|
||||||
# Reads the file, returns a SceneNode (possibly with nested ones), or None
|
# Reads the file, returns a SceneNode (possibly with nested ones), or None
|
||||||
def read(self, file_name):
|
def read(self, file_name):
|
||||||
try:
|
try:
|
||||||
self.defs = {}
|
self.defs = {}
|
||||||
self.shapes = []
|
self.shapes = []
|
||||||
|
|
||||||
tree = ET.parse(file_name)
|
tree = ET.parse(file_name)
|
||||||
xml_root = tree.getroot()
|
xml_root = tree.getroot()
|
||||||
|
|
||||||
if xml_root.tag != "X3D":
|
if xml_root.tag != "X3D":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
|
scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
|
||||||
if xml_root[0].tag == "head":
|
if xml_root[0].tag == "head":
|
||||||
for head_node in xml_root[0]:
|
for head_node in xml_root[0]:
|
||||||
if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
|
if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
|
||||||
scale *= float(head_node.attrib["conversionFactor"])
|
scale *= float(head_node.attrib["conversionFactor"])
|
||||||
break
|
break
|
||||||
xml_scene = xml_root[1]
|
xml_scene = xml_root[1]
|
||||||
else:
|
else:
|
||||||
xml_scene = xml_root[0]
|
xml_scene = xml_root[0]
|
||||||
|
|
||||||
if xml_scene.tag != "Scene":
|
if xml_scene.tag != "Scene":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.transform = Matrix()
|
self.transform = Matrix()
|
||||||
self.transform.setByScaleFactor(scale)
|
self.transform.setByScaleFactor(scale)
|
||||||
self.index_base = 0
|
self.index_base = 0
|
||||||
|
|
||||||
# Traverse the scene tree, populate the shapes list
|
# Traverse the scene tree, populate the shapes list
|
||||||
self.processChildNodes(xml_scene)
|
self.processChildNodes(xml_scene)
|
||||||
|
|
||||||
if self.shapes:
|
if self.shapes:
|
||||||
builder = MeshBuilder()
|
builder = MeshBuilder()
|
||||||
builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
|
builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
|
||||||
|
@ -95,20 +96,20 @@ class X3DReader(MeshReader):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
Logger.logException("e", "Exception in X3D reader")
|
Logger.logException("e", "Exception in X3D reader")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
# ------------------------- XML tree traversal
|
# ------------------------- XML tree traversal
|
||||||
|
|
||||||
def processNode(self, xml_node):
|
def processNode(self, xml_node):
|
||||||
xml_node = self.resolveDefUse(xml_node)
|
xml_node = self.resolveDefUse(xml_node)
|
||||||
if xml_node is None:
|
if xml_node is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
tag = xml_node.tag
|
tag = xml_node.tag
|
||||||
if tag in ("Group", "StaticGroup", "CADAssembly", "CADFace", "CADLayer", "Collision"):
|
if tag in ("Group", "StaticGroup", "CADAssembly", "CADFace", "CADLayer", "Collision"):
|
||||||
self.processChildNodes(xml_node)
|
self.processChildNodes(xml_node)
|
||||||
|
@ -120,8 +121,8 @@ class X3DReader(MeshReader):
|
||||||
self.processTransform(xml_node)
|
self.processTransform(xml_node)
|
||||||
elif tag == "Shape":
|
elif tag == "Shape":
|
||||||
self.processShape(xml_node)
|
self.processShape(xml_node)
|
||||||
|
|
||||||
|
|
||||||
def processShape(self, xml_node):
|
def processShape(self, xml_node):
|
||||||
# Find the geometry and the appearance inside the Shape
|
# Find the geometry and the appearance inside the Shape
|
||||||
geometry = appearance = None
|
geometry = appearance = None
|
||||||
|
@ -130,21 +131,21 @@ class X3DReader(MeshReader):
|
||||||
appearance = self.resolveDefUse(sub_node)
|
appearance = self.resolveDefUse(sub_node)
|
||||||
elif sub_node.tag in self.geometry_importers and not geometry:
|
elif sub_node.tag in self.geometry_importers and not geometry:
|
||||||
geometry = self.resolveDefUse(sub_node)
|
geometry = self.resolveDefUse(sub_node)
|
||||||
|
|
||||||
# TODO: appearance is completely ignored. At least apply the material color...
|
# TODO: appearance is completely ignored. At least apply the material color...
|
||||||
if not geometry is None:
|
if not geometry is None:
|
||||||
try:
|
try:
|
||||||
self.verts = self.faces = [] # Safeguard
|
self.verts = self.faces = [] # Safeguard
|
||||||
self.geometry_importers[geometry.tag](self, geometry)
|
self.geometry_importers[geometry.tag](self, geometry)
|
||||||
m = self.transform.getData()
|
m = self.transform.getData()
|
||||||
verts = m.dot(self.verts)[:3].transpose()
|
verts = m.dot(self.verts)[:3].transpose()
|
||||||
|
|
||||||
self.shapes.append(Shape(verts, self.faces, self.index_base, geometry.tag))
|
self.shapes.append(Shape(verts, self.faces, self.index_base, geometry.tag))
|
||||||
self.index_base += len(verts)
|
self.index_base += len(verts)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
Logger.logException("e", "Exception in X3D reader while reading %s", geometry.tag)
|
Logger.logException("e", "Exception in X3D reader while reading %s", geometry.tag)
|
||||||
|
|
||||||
# Returns the referenced node if the node has USE, the same node otherwise.
|
# Returns the referenced node if the node has USE, the same node otherwise.
|
||||||
# May return None is USE points at a nonexistent node
|
# May return None is USE points at a nonexistent node
|
||||||
# In X3DOM, when both DEF and USE are in the same node, DEF is ignored.
|
# In X3DOM, when both DEF and USE are in the same node, DEF is ignored.
|
||||||
|
@ -155,34 +156,34 @@ class X3DReader(MeshReader):
|
||||||
if USE:
|
if USE:
|
||||||
return self.defs.get(USE, None)
|
return self.defs.get(USE, None)
|
||||||
|
|
||||||
DEF = node.attrib.get("DEF")
|
DEF = node.attrib.get("DEF")
|
||||||
if DEF:
|
if DEF:
|
||||||
self.defs[DEF] = node
|
self.defs[DEF] = node
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def processChildNodes(self, node):
|
def processChildNodes(self, node):
|
||||||
for c in node:
|
for c in node:
|
||||||
self.processNode(c)
|
self.processNode(c)
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
# Since this is a grouping node, will recurse down the tree.
|
# Since this is a grouping node, will recurse down the tree.
|
||||||
# According to the spec, the final transform matrix is:
|
# According to the spec, the final transform matrix is:
|
||||||
# T * C * R * SR * S * -SR * -C
|
# T * C * R * SR * S * -SR * -C
|
||||||
# Where SR corresponds to the rotation matrix to scaleOrientation
|
# Where SR corresponds to the rotation matrix to scaleOrientation
|
||||||
# C and SR are rather exotic. S, slightly less so.
|
# C and SR are rather exotic. S, slightly less so.
|
||||||
def processTransform(self, node):
|
def processTransform(self, node):
|
||||||
rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
|
rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
|
||||||
trans = readVector(node, "translation", (0, 0, 0)) # Vector
|
trans = readVector(node, "translation", (0, 0, 0)) # Vector
|
||||||
scale = readVector(node, "scale", (1, 1, 1)) # Vector
|
scale = readVector(node, "scale", (1, 1, 1)) # Vector
|
||||||
center = readVector(node, "center", (0, 0, 0)) # Vector
|
center = readVector(node, "center", (0, 0, 0)) # Vector
|
||||||
scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
|
scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
|
||||||
|
|
||||||
# Store the previous transform; in Cura, the default matrix multiplication is in place
|
# Store the previous transform; in Cura, the default matrix multiplication is in place
|
||||||
prev = Matrix(self.transform.getData()) # It's deep copy, I've checked
|
prev = Matrix(self.transform.getData()) # It's deep copy, I've checked
|
||||||
|
|
||||||
# The rest of transform manipulation will be applied in place
|
# The rest of transform manipulation will be applied in place
|
||||||
got_center = (center.x != 0 or center.y != 0 or center.z != 0)
|
got_center = (center.x != 0 or center.y != 0 or center.z != 0)
|
||||||
|
|
||||||
T = self.transform
|
T = self.transform
|
||||||
if trans.x != 0 or trans.y != 0 or trans.z !=0:
|
if trans.x != 0 or trans.y != 0 or trans.z !=0:
|
||||||
T.translate(trans)
|
T.translate(trans)
|
||||||
|
@ -202,13 +203,13 @@ class X3DReader(MeshReader):
|
||||||
T.rotateByAxis(-scale_orient[0], scale_orient[1])
|
T.rotateByAxis(-scale_orient[0], scale_orient[1])
|
||||||
if got_center:
|
if got_center:
|
||||||
T.translate(-center)
|
T.translate(-center)
|
||||||
|
|
||||||
self.processChildNodes(node)
|
self.processChildNodes(node)
|
||||||
self.transform = prev
|
self.transform = prev
|
||||||
|
|
||||||
# ------------------------- Geometry importers
|
# ------------------------- Geometry importers
|
||||||
# They are supposed to fill the self.verts and self.faces arrays, the caller will do the rest
|
# They are supposed to fill the self.verts and self.faces arrays, the caller will do the rest
|
||||||
|
|
||||||
# Primitives
|
# Primitives
|
||||||
|
|
||||||
def processGeometryBox(self, node):
|
def processGeometryBox(self, node):
|
||||||
|
@ -228,14 +229,14 @@ class X3DReader(MeshReader):
|
||||||
self.addVertex(-dx, -dy, dz)
|
self.addVertex(-dx, -dy, dz)
|
||||||
self.addVertex(-dx, -dy, -dz)
|
self.addVertex(-dx, -dy, -dz)
|
||||||
self.addVertex(dx, -dy, -dz)
|
self.addVertex(dx, -dy, -dz)
|
||||||
|
|
||||||
self.addQuad(0, 1, 2, 3) # +y
|
self.addQuad(0, 1, 2, 3) # +y
|
||||||
self.addQuad(4, 0, 3, 7) # +x
|
self.addQuad(4, 0, 3, 7) # +x
|
||||||
self.addQuad(7, 3, 2, 6) # -z
|
self.addQuad(7, 3, 2, 6) # -z
|
||||||
self.addQuad(6, 2, 1, 5) # -x
|
self.addQuad(6, 2, 1, 5) # -x
|
||||||
self.addQuad(5, 1, 0, 4) # +z
|
self.addQuad(5, 1, 0, 4) # +z
|
||||||
self.addQuad(7, 6, 5, 4) # -y
|
self.addQuad(7, 6, 5, 4) # -y
|
||||||
|
|
||||||
# The sphere is subdivided into nr rings and ns segments
|
# The sphere is subdivided into nr rings and ns segments
|
||||||
def processGeometrySphere(self, node):
|
def processGeometrySphere(self, node):
|
||||||
r = readFloat(node, "radius", 0.5)
|
r = readFloat(node, "radius", 0.5)
|
||||||
|
@ -247,16 +248,16 @@ class X3DReader(MeshReader):
|
||||||
(nr, ns) = subdiv
|
(nr, ns) = subdiv
|
||||||
else:
|
else:
|
||||||
nr = ns = DEFAULT_SUBDIV
|
nr = ns = DEFAULT_SUBDIV
|
||||||
|
|
||||||
lau = pi / nr # Unit angle of latitude (rings) for the given tesselation
|
lau = pi / nr # Unit angle of latitude (rings) for the given tesselation
|
||||||
lou = 2 * pi / ns # Unit angle of longitude (segments)
|
lou = 2 * pi / ns # Unit angle of longitude (segments)
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns)
|
self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns)
|
||||||
|
|
||||||
# +y and -y poles
|
# +y and -y poles
|
||||||
self.addVertex(0, r, 0)
|
self.addVertex(0, r, 0)
|
||||||
self.addVertex(0, -r, 0)
|
self.addVertex(0, -r, 0)
|
||||||
|
|
||||||
# The non-polar vertices go from x=0, negative z plane counterclockwise -
|
# The non-polar vertices go from x=0, negative z plane counterclockwise -
|
||||||
# to -x, to +z, to +x, back to -z
|
# to -x, to +z, to +x, back to -z
|
||||||
for ring in range(1, nr):
|
for ring in range(1, nr):
|
||||||
|
@ -264,12 +265,12 @@ class X3DReader(MeshReader):
|
||||||
self.addVertex(-r*sin(lou * seg) * sin(lau * ring),
|
self.addVertex(-r*sin(lou * seg) * sin(lau * ring),
|
||||||
r*cos(lau * ring),
|
r*cos(lau * ring),
|
||||||
-r*cos(lou * seg) * sin(lau * ring))
|
-r*cos(lou * seg) * sin(lau * ring))
|
||||||
|
|
||||||
vb = 2 + (nr - 2) * ns # First vertex index for the bottom cap
|
vb = 2 + (nr - 2) * ns # First vertex index for the bottom cap
|
||||||
|
|
||||||
# Faces go in order: top cap, sides, bottom cap.
|
# Faces go in order: top cap, sides, bottom cap.
|
||||||
# Sides go by ring then by segment.
|
# Sides go by ring then by segment.
|
||||||
|
|
||||||
# Caps
|
# Caps
|
||||||
# Top cap face vertices go in order: down right up
|
# Top cap face vertices go in order: down right up
|
||||||
# (starting from +y pole)
|
# (starting from +y pole)
|
||||||
|
@ -277,7 +278,7 @@ class X3DReader(MeshReader):
|
||||||
for seg in range(ns):
|
for seg in range(ns):
|
||||||
self.addTri(0, seg + 2, (seg + 1) % ns + 2)
|
self.addTri(0, seg + 2, (seg + 1) % ns + 2)
|
||||||
self.addTri(1, vb + (seg + 1) % ns, vb + seg)
|
self.addTri(1, vb + (seg + 1) % ns, vb + seg)
|
||||||
|
|
||||||
# Sides
|
# Sides
|
||||||
# Side face vertices go in order: down right upleft, downright up left
|
# Side face vertices go in order: down right upleft, downright up left
|
||||||
for ring in range(nr - 2):
|
for ring in range(nr - 2):
|
||||||
|
@ -288,24 +289,24 @@ class X3DReader(MeshReader):
|
||||||
for seg in range(ns):
|
for seg in range(ns):
|
||||||
nseg = (seg + 1) % ns
|
nseg = (seg + 1) % ns
|
||||||
self.addQuad(tvb + seg, bvb + seg, bvb + nseg, tvb + nseg)
|
self.addQuad(tvb + seg, bvb + seg, bvb + nseg, tvb + nseg)
|
||||||
|
|
||||||
def processGeometryCone(self, node):
|
def processGeometryCone(self, node):
|
||||||
r = readFloat(node, "bottomRadius", 1)
|
r = readFloat(node, "bottomRadius", 1)
|
||||||
height = readFloat(node, "height", 2)
|
height = readFloat(node, "height", 2)
|
||||||
bottom = readBoolean(node, "bottom", True)
|
bottom = readBoolean(node, "bottom", True)
|
||||||
side = readBoolean(node, "side", True)
|
side = readBoolean(node, "side", True)
|
||||||
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
||||||
|
|
||||||
d = height / 2
|
d = height / 2
|
||||||
angle = 2 * pi / n
|
angle = 2 * pi / n
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount((n if side else 0) + (n-2 if bottom else 0), n+1)
|
self.reserveFaceAndVertexCount((n if side else 0) + (n-2 if bottom else 0), n+1)
|
||||||
|
|
||||||
# Vertex 0 is the apex, vertices 1..n are the bottom
|
# Vertex 0 is the apex, vertices 1..n are the bottom
|
||||||
self.addVertex(0, d, 0)
|
self.addVertex(0, d, 0)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
self.addVertex(-r * sin(angle * i), -d, -r * cos(angle * i))
|
self.addVertex(-r * sin(angle * i), -d, -r * cos(angle * i))
|
||||||
|
|
||||||
# Side face vertices go: up down right
|
# Side face vertices go: up down right
|
||||||
if side:
|
if side:
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
|
@ -313,7 +314,7 @@ class X3DReader(MeshReader):
|
||||||
if bottom:
|
if bottom:
|
||||||
for i in range(2, n):
|
for i in range(2, n):
|
||||||
self.addTri(1, i, i+1)
|
self.addTri(1, i, i+1)
|
||||||
|
|
||||||
def processGeometryCylinder(self, node):
|
def processGeometryCylinder(self, node):
|
||||||
r = readFloat(node, "radius", 1)
|
r = readFloat(node, "radius", 1)
|
||||||
height = readFloat(node, "height", 2)
|
height = readFloat(node, "height", 2)
|
||||||
|
@ -321,13 +322,13 @@ class X3DReader(MeshReader):
|
||||||
side = readBoolean(node, "side", True)
|
side = readBoolean(node, "side", True)
|
||||||
top = readBoolean(node, "top", True)
|
top = readBoolean(node, "top", True)
|
||||||
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
||||||
|
|
||||||
nn = n * 2
|
nn = n * 2
|
||||||
angle = 2 * pi / n
|
angle = 2 * pi / n
|
||||||
hh = height/2
|
hh = height/2
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount((nn if side else 0) + (n - 2 if top else 0) + (n - 2 if bottom else 0), nn)
|
self.reserveFaceAndVertexCount((nn if side else 0) + (n - 2 if top else 0) + (n - 2 if bottom else 0), nn)
|
||||||
|
|
||||||
# The seam is at x=0, z=-r, vertices go ccw -
|
# The seam is at x=0, z=-r, vertices go ccw -
|
||||||
# to pos x, to neg z, to neg x, back to neg z
|
# to pos x, to neg z, to neg x, back to neg z
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
|
@ -335,18 +336,18 @@ class X3DReader(MeshReader):
|
||||||
rc = -r * cos(angle * i)
|
rc = -r * cos(angle * i)
|
||||||
self.addVertex(rs, hh, rc)
|
self.addVertex(rs, hh, rc)
|
||||||
self.addVertex(rs, -hh, rc)
|
self.addVertex(rs, -hh, rc)
|
||||||
|
|
||||||
if side:
|
if side:
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
ni = (i + 1) % n
|
ni = (i + 1) % n
|
||||||
self.addQuad(ni * 2 + 1, ni * 2, i * 2, i * 2 + 1)
|
self.addQuad(ni * 2 + 1, ni * 2, i * 2, i * 2 + 1)
|
||||||
|
|
||||||
for i in range(2, nn-3, 2):
|
for i in range(2, nn-3, 2):
|
||||||
if top:
|
if top:
|
||||||
self.addTri(0, i, i+2)
|
self.addTri(0, i, i+2)
|
||||||
if bottom:
|
if bottom:
|
||||||
self.addTri(1, i+1, i+3)
|
self.addTri(1, i+1, i+3)
|
||||||
|
|
||||||
# Semi-primitives
|
# Semi-primitives
|
||||||
|
|
||||||
def processGeometryElevationGrid(self, node):
|
def processGeometryElevationGrid(self, node):
|
||||||
|
@ -356,21 +357,21 @@ class X3DReader(MeshReader):
|
||||||
nz = readInt(node, "zDimension", 0)
|
nz = readInt(node, "zDimension", 0)
|
||||||
height = readFloatArray(node, "height", False)
|
height = readFloatArray(node, "height", False)
|
||||||
ccw = readBoolean(node, "ccw", True)
|
ccw = readBoolean(node, "ccw", True)
|
||||||
|
|
||||||
if nx <= 0 or nz <= 0 or len(height) < nx*nz:
|
if nx <= 0 or nz <= 0 or len(height) < nx*nz:
|
||||||
return # That's weird, the wording of the standard suggests grids with zero quads are somehow valid
|
return # That's weird, the wording of the standard suggests grids with zero quads are somehow valid
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount(2*(nx-1)*(nz-1), nx*nz)
|
self.reserveFaceAndVertexCount(2*(nx-1)*(nz-1), nx*nz)
|
||||||
|
|
||||||
for z in range(nz):
|
for z in range(nz):
|
||||||
for x in range(nx):
|
for x in range(nx):
|
||||||
self.addVertex(x * dx, height[z*nx + x], z * dz)
|
self.addVertex(x * dx, height[z*nx + x], z * dz)
|
||||||
|
|
||||||
for z in range(1, nz):
|
for z in range(1, nz):
|
||||||
for x in range(1, nx):
|
for x in range(1, nx):
|
||||||
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x, (z - 1)*nx + x, ccw)
|
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x, (z - 1)*nx + x, ccw)
|
||||||
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x - 1, z*nx + x, ccw)
|
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x - 1, z*nx + x, ccw)
|
||||||
|
|
||||||
def processGeometryExtrusion(self, node):
|
def processGeometryExtrusion(self, node):
|
||||||
ccw = readBoolean(node, "ccw", True)
|
ccw = readBoolean(node, "ccw", True)
|
||||||
begin_cap = readBoolean(node, "beginCap", True)
|
begin_cap = readBoolean(node, "beginCap", True)
|
||||||
|
@ -384,23 +385,23 @@ class X3DReader(MeshReader):
|
||||||
# This converts X3D's axis/angle rotation to a 3x3 numpy matrix
|
# This converts X3D's axis/angle rotation to a 3x3 numpy matrix
|
||||||
def toRotationMatrix(rot):
|
def toRotationMatrix(rot):
|
||||||
(x, y, z) = rot[:3]
|
(x, y, z) = rot[:3]
|
||||||
a = rot[3]
|
a = rot[3]
|
||||||
s = sin(a)
|
s = sin(a)
|
||||||
c = cos(a)
|
c = cos(a)
|
||||||
t = 1-c
|
t = 1-c
|
||||||
return numpy.array((
|
return numpy.array((
|
||||||
(x * x * t + c, x * y * t - z*s, x * z * t + y * s),
|
(x * x * t + c, x * y * t - z*s, x * z * t + y * s),
|
||||||
(x * y * t + z*s, y * y * t + c, y * z * t - x * s),
|
(x * y * t + z*s, y * y * t + c, y * z * t - x * s),
|
||||||
(x * z * t - y * s, y * z * t + x * s, z * z * t + c)))
|
(x * z * t - y * s, y * z * t + x * s, z * z * t + c)))
|
||||||
|
|
||||||
orient = [toRotationMatrix(orient[i:i+4]) if orient[i+3] != 0 else None for i in range(0, len(orient), 4)]
|
orient = [toRotationMatrix(orient[i:i+4]) if orient[i+3] != 0 else None for i in range(0, len(orient), 4)]
|
||||||
|
|
||||||
scale = readFloatArray(node, "scale", None)
|
scale = readFloatArray(node, "scale", None)
|
||||||
if scale:
|
if scale:
|
||||||
scale = [numpy.array(((scale[i], 0, 0), (0, 1, 0), (0, 0, scale[i+1])))
|
scale = [numpy.array(((scale[i], 0, 0), (0, 1, 0), (0, 0, scale[i+1])))
|
||||||
if scale[i] != 1 or scale[i+1] != 1 else None for i in range(0, len(scale), 2)]
|
if scale[i] != 1 or scale[i+1] != 1 else None for i in range(0, len(scale), 2)]
|
||||||
|
|
||||||
|
|
||||||
# Special treatment for the closed spine and cross section.
|
# Special treatment for the closed spine and cross section.
|
||||||
# Let's save some memory by not creating identical but distinct vertices;
|
# Let's save some memory by not creating identical but distinct vertices;
|
||||||
# later we'll introduce conditional logic to link the last vertex with
|
# later we'll introduce conditional logic to link the last vertex with
|
||||||
|
@ -413,14 +414,14 @@ class X3DReader(MeshReader):
|
||||||
ncf = nc if crossClosed else nc - 1
|
ncf = nc if crossClosed else nc - 1
|
||||||
# Face count along the cross; for closed cross, it's the same as the
|
# Face count along the cross; for closed cross, it's the same as the
|
||||||
# respective vertex count
|
# respective vertex count
|
||||||
|
|
||||||
spine_closed = spine[0] == spine[-1]
|
spine_closed = spine[0] == spine[-1]
|
||||||
if spine_closed:
|
if spine_closed:
|
||||||
spine = spine[:-1]
|
spine = spine[:-1]
|
||||||
ns = len(spine)
|
ns = len(spine)
|
||||||
spine = [Vector(*s) for s in spine]
|
spine = [Vector(*s) for s in spine]
|
||||||
nsf = ns if spine_closed else ns - 1
|
nsf = ns if spine_closed else ns - 1
|
||||||
|
|
||||||
# This will be used for fallback, where the current spine point joins
|
# This will be used for fallback, where the current spine point joins
|
||||||
# two collinear spine segments. No need to recheck the case of the
|
# two collinear spine segments. No need to recheck the case of the
|
||||||
# closed spine/last-to-first point juncture; if there's an angle there,
|
# closed spine/last-to-first point juncture; if there's an angle there,
|
||||||
|
@ -442,7 +443,7 @@ class X3DReader(MeshReader):
|
||||||
if v.cross(orig_y).length() > EPSILON:
|
if v.cross(orig_y).length() > EPSILON:
|
||||||
# Spine at angle with global y - rotate the z accordingly
|
# Spine at angle with global y - rotate the z accordingly
|
||||||
a = v.cross(orig_y) # Axis of rotation to get to the Z
|
a = v.cross(orig_y) # Axis of rotation to get to the Z
|
||||||
(x, y, z) = a.normalized().getData()
|
(x, y, z) = a.normalized().getData()
|
||||||
s = a.length()/v.length()
|
s = a.length()/v.length()
|
||||||
c = sqrt(1-s*s)
|
c = sqrt(1-s*s)
|
||||||
t = 1-c
|
t = 1-c
|
||||||
|
@ -452,7 +453,7 @@ class X3DReader(MeshReader):
|
||||||
(x * z * t + y * s, y * z * t - x * s, z * z * t + c)))
|
(x * z * t + y * s, y * z * t - x * s, z * z * t + c)))
|
||||||
orig_z = Vector(*m.dot(orig_z.getData()))
|
orig_z = Vector(*m.dot(orig_z.getData()))
|
||||||
return orig_z
|
return orig_z
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount(2*nsf*ncf + (nc - 2 if begin_cap else 0) + (nc - 2 if end_cap else 0), ns*nc)
|
self.reserveFaceAndVertexCount(2*nsf*ncf + (nc - 2 if begin_cap else 0) + (nc - 2 if end_cap else 0), ns*nc)
|
||||||
|
|
||||||
z = None
|
z = None
|
||||||
|
@ -482,151 +483,151 @@ class X3DReader(MeshReader):
|
||||||
y = spt - sprev
|
y = spt - sprev
|
||||||
# If there's more than one point in the spine, z is already set.
|
# If there's more than one point in the spine, z is already set.
|
||||||
# One point in the spline is an error anyway.
|
# One point in the spline is an error anyway.
|
||||||
|
|
||||||
z = z.normalized()
|
z = z.normalized()
|
||||||
y = y.normalized()
|
y = y.normalized()
|
||||||
x = y.cross(z) # Already normalized
|
x = y.cross(z) # Already normalized
|
||||||
m = numpy.array(((x.x, y.x, z.x), (x.y, y.y, z.y), (x.z, y.z, z.z)))
|
m = numpy.array(((x.x, y.x, z.x), (x.y, y.y, z.y), (x.z, y.z, z.z)))
|
||||||
|
|
||||||
# Columns are the unit vectors for the xz plane for the cross-section
|
# Columns are the unit vectors for the xz plane for the cross-section
|
||||||
if orient:
|
if orient:
|
||||||
mrot = orient[i] if len(orient) > 1 else orient[0]
|
mrot = orient[i] if len(orient) > 1 else orient[0]
|
||||||
if not mrot is None:
|
if not mrot is None:
|
||||||
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
|
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
|
||||||
|
|
||||||
if scale:
|
if scale:
|
||||||
mscale = scale[i] if len(scale) > 1 else scale[0]
|
mscale = scale[i] if len(scale) > 1 else scale[0]
|
||||||
if not mscale is None:
|
if not mscale is None:
|
||||||
m = m.dot(mscale)
|
m = m.dot(mscale)
|
||||||
|
|
||||||
# First the cross-section 2-vector is scaled,
|
# First the cross-section 2-vector is scaled,
|
||||||
# then rotated (which may make it a 3-vector),
|
# then rotated (which may make it a 3-vector),
|
||||||
# then applied to the xz plane unit vectors
|
# then applied to the xz plane unit vectors
|
||||||
|
|
||||||
sptv3 = numpy.array(spt.getData()[:3])
|
sptv3 = numpy.array(spt.getData()[:3])
|
||||||
for cpt in cross:
|
for cpt in cross:
|
||||||
v = sptv3 + m.dot(cpt)
|
v = sptv3 + m.dot(cpt)
|
||||||
self.addVertex(*v)
|
self.addVertex(*v)
|
||||||
|
|
||||||
if begin_cap:
|
if begin_cap:
|
||||||
self.addFace([x for x in range(nc - 1, -1, -1)], ccw)
|
self.addFace([x for x in range(nc - 1, -1, -1)], ccw)
|
||||||
|
|
||||||
# Order of edges in the face: forward along cross, forward along spine,
|
# Order of edges in the face: forward along cross, forward along spine,
|
||||||
# backward along cross, backward along spine, flipped if now ccw.
|
# backward along cross, backward along spine, flipped if now ccw.
|
||||||
# This order is assumed later in the texture coordinate assignment;
|
# This order is assumed later in the texture coordinate assignment;
|
||||||
# please don't change without syncing.
|
# please don't change without syncing.
|
||||||
|
|
||||||
for s in range(ns - 1):
|
for s in range(ns - 1):
|
||||||
for c in range(ncf):
|
for c in range(ncf):
|
||||||
self.addQuadFlip(s * nc + c, s * nc + (c + 1) % nc,
|
self.addQuadFlip(s * nc + c, s * nc + (c + 1) % nc,
|
||||||
(s + 1) * nc + (c + 1) % nc, (s + 1) * nc + c, ccw)
|
(s + 1) * nc + (c + 1) % nc, (s + 1) * nc + c, ccw)
|
||||||
|
|
||||||
if spine_closed:
|
if spine_closed:
|
||||||
# The faces between the last and the first spine points
|
# The faces between the last and the first spine points
|
||||||
b = (ns - 1) * nc
|
b = (ns - 1) * nc
|
||||||
for c in range(ncf):
|
for c in range(ncf):
|
||||||
self.addQuadFlip(b + c, b + (c + 1) % nc,
|
self.addQuadFlip(b + c, b + (c + 1) % nc,
|
||||||
(c + 1) % nc, c, ccw)
|
(c + 1) % nc, c, ccw)
|
||||||
|
|
||||||
if end_cap:
|
if end_cap:
|
||||||
self.addFace([(ns - 1) * nc + x for x in range(0, nc)], ccw)
|
self.addFace([(ns - 1) * nc + x for x in range(0, nc)], ccw)
|
||||||
|
|
||||||
# Triangle meshes
|
# Triangle meshes
|
||||||
|
|
||||||
# Helper for numerous nodes with a Coordinate subnode holding vertices
|
# Helper for numerous nodes with a Coordinate subnode holding vertices
|
||||||
# That all triangle meshes and IndexedFaceSet
|
# That all triangle meshes and IndexedFaceSet
|
||||||
# num_faces can be a function, in case the face count is a function of vertex count
|
# num_faces can be a function, in case the face count is a function of vertex count
|
||||||
def startCoordMesh(self, node, num_faces):
|
def startCoordMesh(self, node, num_faces):
|
||||||
ccw = readBoolean(node, "ccw", True)
|
ccw = readBoolean(node, "ccw", True)
|
||||||
self.readVertices(node) # This will allocate and fill the vertex array
|
self.readVertices(node) # This will allocate and fill the vertex array
|
||||||
if hasattr(num_faces, "__call__"):
|
if hasattr(num_faces, "__call__"):
|
||||||
num_faces = num_faces(self.getVertexCount())
|
num_faces = num_faces(self.getVertexCount())
|
||||||
self.reserveFaceCount(num_faces)
|
self.reserveFaceCount(num_faces)
|
||||||
|
|
||||||
return ccw
|
return ccw
|
||||||
|
|
||||||
|
|
||||||
def processGeometryIndexedTriangleSet(self, node):
|
def processGeometryIndexedTriangleSet(self, node):
|
||||||
index = readIntArray(node, "index", [])
|
index = readIntArray(node, "index", [])
|
||||||
num_faces = len(index) // 3
|
num_faces = len(index) // 3
|
||||||
ccw = int(self.startCoordMesh(node, num_faces))
|
ccw = int(self.startCoordMesh(node, num_faces))
|
||||||
|
|
||||||
for i in range(0, num_faces*3, 3):
|
for i in range(0, num_faces*3, 3):
|
||||||
self.addTri(index[i + 1 - ccw], index[i + ccw], index[i+2])
|
self.addTri(index[i + 1 - ccw], index[i + ccw], index[i+2])
|
||||||
|
|
||||||
def processGeometryIndexedTriangleStripSet(self, node):
|
def processGeometryIndexedTriangleStripSet(self, node):
|
||||||
strips = readIndex(node, "index")
|
strips = readIndex(node, "index")
|
||||||
ccw = int(self.startCoordMesh(node, sum([len(strip) - 2 for strip in strips])))
|
ccw = int(self.startCoordMesh(node, sum([len(strip) - 2 for strip in strips])))
|
||||||
|
|
||||||
for strip in strips:
|
for strip in strips:
|
||||||
sccw = ccw # Running CCW value, reset for each strip
|
sccw = ccw # Running CCW value, reset for each strip
|
||||||
for i in range(len(strip) - 2):
|
for i in range(len(strip) - 2):
|
||||||
self.addTri(strip[i + 1 - sccw], strip[i + sccw], strip[i+2])
|
self.addTri(strip[i + 1 - sccw], strip[i + sccw], strip[i+2])
|
||||||
sccw = 1 - sccw
|
sccw = 1 - sccw
|
||||||
|
|
||||||
def processGeometryIndexedTriangleFanSet(self, node):
|
def processGeometryIndexedTriangleFanSet(self, node):
|
||||||
fans = readIndex(node, "index")
|
fans = readIndex(node, "index")
|
||||||
ccw = int(self.startCoordMesh(node, sum([len(fan) - 2 for fan in fans])))
|
ccw = int(self.startCoordMesh(node, sum([len(fan) - 2 for fan in fans])))
|
||||||
|
|
||||||
for fan in fans:
|
for fan in fans:
|
||||||
for i in range(1, len(fan) - 1):
|
for i in range(1, len(fan) - 1):
|
||||||
self.addTri(fan[0], fan[i + 1 - ccw], fan[i + ccw])
|
self.addTri(fan[0], fan[i + 1 - ccw], fan[i + ccw])
|
||||||
|
|
||||||
def processGeometryTriangleSet(self, node):
|
def processGeometryTriangleSet(self, node):
|
||||||
ccw = int(self.startCoordMesh(node, lambda num_vert: num_vert // 3))
|
ccw = int(self.startCoordMesh(node, lambda num_vert: num_vert // 3))
|
||||||
for i in range(0, self.getVertexCount(), 3):
|
for i in range(0, self.getVertexCount(), 3):
|
||||||
self.addTri(i + 1 - ccw, i + ccw, i+2)
|
self.addTri(i + 1 - ccw, i + ccw, i+2)
|
||||||
|
|
||||||
def processGeometryTriangleStripSet(self, node):
|
def processGeometryTriangleStripSet(self, node):
|
||||||
strips = readIntArray(node, "stripCount", [])
|
strips = readIntArray(node, "stripCount", [])
|
||||||
ccw = int(self.startCoordMesh(node, sum([n-2 for n in strips])))
|
ccw = int(self.startCoordMesh(node, sum([n-2 for n in strips])))
|
||||||
|
|
||||||
vb = 0
|
vb = 0
|
||||||
for n in strips:
|
for n in strips:
|
||||||
sccw = ccw
|
sccw = ccw
|
||||||
for i in range(n-2):
|
for i in range(n-2):
|
||||||
self.addTri(vb + i + 1 - sccw, vb + i + sccw, vb + i + 2)
|
self.addTri(vb + i + 1 - sccw, vb + i + sccw, vb + i + 2)
|
||||||
sccw = 1 - sccw
|
sccw = 1 - sccw
|
||||||
vb += n
|
vb += n
|
||||||
|
|
||||||
def processGeometryTriangleFanSet(self, node):
|
def processGeometryTriangleFanSet(self, node):
|
||||||
fans = readIntArray(node, "fanCount", [])
|
fans = readIntArray(node, "fanCount", [])
|
||||||
ccw = int(self.startCoordMesh(node, sum([n-2 for n in fans])))
|
ccw = int(self.startCoordMesh(node, sum([n-2 for n in fans])))
|
||||||
|
|
||||||
vb = 0
|
vb = 0
|
||||||
for n in fans:
|
for n in fans:
|
||||||
for i in range(1, n-1):
|
for i in range(1, n-1):
|
||||||
self.addTri(vb, vb + i + 1 - ccw, vb + i + ccw)
|
self.addTri(vb, vb + i + 1 - ccw, vb + i + ccw)
|
||||||
vb += n
|
vb += n
|
||||||
|
|
||||||
# Quad geometries from the CAD module, might be relevant for printing
|
# Quad geometries from the CAD module, might be relevant for printing
|
||||||
|
|
||||||
def processGeometryQuadSet(self, node):
|
def processGeometryQuadSet(self, node):
|
||||||
ccw = self.startCoordMesh(node, lambda num_vert: 2*(num_vert // 4))
|
ccw = self.startCoordMesh(node, lambda num_vert: 2*(num_vert // 4))
|
||||||
for i in range(0, self.getVertexCount(), 4):
|
for i in range(0, self.getVertexCount(), 4):
|
||||||
self.addQuadFlip(i, i+1, i+2, i+3, ccw)
|
self.addQuadFlip(i, i+1, i+2, i+3, ccw)
|
||||||
|
|
||||||
def processGeometryIndexedQuadSet(self, node):
|
def processGeometryIndexedQuadSet(self, node):
|
||||||
index = readIntArray(node, "index", [])
|
index = readIntArray(node, "index", [])
|
||||||
num_quads = len(index) // 4
|
num_quads = len(index) // 4
|
||||||
ccw = self.startCoordMesh(node, num_quads*2)
|
ccw = self.startCoordMesh(node, num_quads*2)
|
||||||
|
|
||||||
for i in range(0, num_quads*4, 4):
|
for i in range(0, num_quads*4, 4):
|
||||||
self.addQuadFlip(index[i], index[i+1], index[i+2], index[i+3], ccw)
|
self.addQuadFlip(index[i], index[i+1], index[i+2], index[i+3], ccw)
|
||||||
|
|
||||||
# 2D polygon geometries
|
# 2D polygon geometries
|
||||||
# Won't work for now, since Cura expects every mesh to have a nontrivial convex hull
|
# Won't work for now, since Cura expects every mesh to have a nontrivial convex hull
|
||||||
# The only way around that is merging meshes.
|
# The only way around that is merging meshes.
|
||||||
|
|
||||||
def processGeometryDisk2D(self, node):
|
def processGeometryDisk2D(self, node):
|
||||||
innerRadius = readFloat(node, "innerRadius", 0)
|
innerRadius = readFloat(node, "innerRadius", 0)
|
||||||
outerRadius = readFloat(node, "outerRadius", 1)
|
outerRadius = readFloat(node, "outerRadius", 1)
|
||||||
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
|
||||||
|
|
||||||
angle = 2 * pi / n
|
angle = 2 * pi / n
|
||||||
|
|
||||||
self.reserveFaceAndVertexCount(n*4 if innerRadius else n-2, n*2 if innerRadius else n)
|
self.reserveFaceAndVertexCount(n*4 if innerRadius else n-2, n*2 if innerRadius else n)
|
||||||
|
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
s = sin(angle * i)
|
s = sin(angle * i)
|
||||||
c = cos(angle * i)
|
c = cos(angle * i)
|
||||||
|
@ -635,11 +636,11 @@ class X3DReader(MeshReader):
|
||||||
self.addVertex(innerRadius*c, innerRadius*s, 0)
|
self.addVertex(innerRadius*c, innerRadius*s, 0)
|
||||||
ni = (i+1) % n
|
ni = (i+1) % n
|
||||||
self.addQuad(2*i, 2*ni, 2*ni+1, 2*i+1)
|
self.addQuad(2*i, 2*ni, 2*ni+1, 2*i+1)
|
||||||
|
|
||||||
if not innerRadius:
|
if not innerRadius:
|
||||||
for i in range(2, n):
|
for i in range(2, n):
|
||||||
self.addTri(0, i-1, i)
|
self.addTri(0, i-1, i)
|
||||||
|
|
||||||
def processGeometryRectangle2D(self, node):
|
def processGeometryRectangle2D(self, node):
|
||||||
(x, y) = readFloatArray(node, "size", (2, 2))
|
(x, y) = readFloatArray(node, "size", (2, 2))
|
||||||
self.reserveFaceAndVertexCount(2, 4)
|
self.reserveFaceAndVertexCount(2, 4)
|
||||||
|
@ -648,7 +649,7 @@ class X3DReader(MeshReader):
|
||||||
self.addVertex(x/2, y/2, 0)
|
self.addVertex(x/2, y/2, 0)
|
||||||
self.addVertex(-x/2, y/2, 0)
|
self.addVertex(-x/2, y/2, 0)
|
||||||
self.addQuad(0, 1, 2, 3)
|
self.addQuad(0, 1, 2, 3)
|
||||||
|
|
||||||
def processGeometryTriangleSet2D(self, node):
|
def processGeometryTriangleSet2D(self, node):
|
||||||
verts = readFloatArray(node, "vertices", ())
|
verts = readFloatArray(node, "vertices", ())
|
||||||
num_faces = len(verts) // 6;
|
num_faces = len(verts) // 6;
|
||||||
|
@ -656,25 +657,25 @@ class X3DReader(MeshReader):
|
||||||
self.reserveFaceAndVertexCount(num_faces, num_faces * 3)
|
self.reserveFaceAndVertexCount(num_faces, num_faces * 3)
|
||||||
for vert in verts:
|
for vert in verts:
|
||||||
self.addVertex(*vert)
|
self.addVertex(*vert)
|
||||||
|
|
||||||
# The front face is on the +Z side, so CCW is a variable
|
# The front face is on the +Z side, so CCW is a variable
|
||||||
for i in range(0, num_faces*3, 3):
|
for i in range(0, num_faces*3, 3):
|
||||||
a = Vector(*verts[i+2]) - Vector(*verts[i])
|
a = Vector(*verts[i+2]) - Vector(*verts[i])
|
||||||
b = Vector(*verts[i+1]) - Vector(*verts[i])
|
b = Vector(*verts[i+1]) - Vector(*verts[i])
|
||||||
self.addTriFlip(i, i+1, i+2, a.x*b.y > a.y*b.x)
|
self.addTriFlip(i, i+1, i+2, a.x*b.y > a.y*b.x)
|
||||||
|
|
||||||
# General purpose polygon mesh
|
# General purpose polygon mesh
|
||||||
|
|
||||||
def processGeometryIndexedFaceSet(self, node):
|
def processGeometryIndexedFaceSet(self, node):
|
||||||
faces = readIndex(node, "coordIndex")
|
faces = readIndex(node, "coordIndex")
|
||||||
ccw = self.startCoordMesh(node, sum([len(face) - 2 for face in faces]))
|
ccw = self.startCoordMesh(node, sum([len(face) - 2 for face in faces]))
|
||||||
|
|
||||||
for face in faces:
|
for face in faces:
|
||||||
if len(face) == 3:
|
if len(face) == 3:
|
||||||
self.addTriFlip(face[0], face[1], face[2], ccw)
|
self.addTriFlip(face[0], face[1], face[2], ccw)
|
||||||
elif len(face) > 3:
|
elif len(face) > 3:
|
||||||
self.addFace(face, ccw)
|
self.addFace(face, ccw)
|
||||||
|
|
||||||
geometry_importers = {
|
geometry_importers = {
|
||||||
"IndexedFaceSet": processGeometryIndexedFaceSet,
|
"IndexedFaceSet": processGeometryIndexedFaceSet,
|
||||||
"IndexedTriangleSet": processGeometryIndexedTriangleSet,
|
"IndexedTriangleSet": processGeometryIndexedTriangleSet,
|
||||||
|
@ -695,7 +696,7 @@ class X3DReader(MeshReader):
|
||||||
"Cylinder": processGeometryCylinder,
|
"Cylinder": processGeometryCylinder,
|
||||||
"Cone": processGeometryCone
|
"Cone": processGeometryCone
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parses the Coordinate.@point field, fills the verts array.
|
# Parses the Coordinate.@point field, fills the verts array.
|
||||||
def readVertices(self, node):
|
def readVertices(self, node):
|
||||||
for c in node:
|
for c in node:
|
||||||
|
@ -704,9 +705,9 @@ class X3DReader(MeshReader):
|
||||||
if not c is None:
|
if not c is None:
|
||||||
pt = c.attrib.get("point")
|
pt = c.attrib.get("point")
|
||||||
if pt:
|
if pt:
|
||||||
# allow the list of float values in 'point' attribute to
|
# allow the list of float values in 'point' attribute to
|
||||||
# be separated by commas or whitespace as per spec of
|
# be separated by commas or whitespace as per spec of
|
||||||
# XML encoding of X3D
|
# XML encoding of X3D
|
||||||
# Ref ISO/IEC 19776-1:2015 : Section 5.1.2
|
# Ref ISO/IEC 19776-1:2015 : Section 5.1.2
|
||||||
co = [float(x) for vec in pt.split(',') for x in vec.split()]
|
co = [float(x) for vec in pt.split(',') for x in vec.split()]
|
||||||
num_verts = len(co) // 3
|
num_verts = len(co) // 3
|
||||||
|
@ -715,57 +716,57 @@ class X3DReader(MeshReader):
|
||||||
# Group by three
|
# Group by three
|
||||||
for i in range(num_verts):
|
for i in range(num_verts):
|
||||||
self.verts[:3,i] = co[3*i:3*i+3]
|
self.verts[:3,i] = co[3*i:3*i+3]
|
||||||
|
|
||||||
# Mesh builder helpers
|
# Mesh builder helpers
|
||||||
|
|
||||||
def reserveFaceAndVertexCount(self, num_faces, num_verts):
|
def reserveFaceAndVertexCount(self, num_faces, num_verts):
|
||||||
# Unlike the Cura MeshBuilder, we use 4-vectors stored as columns for easier transform
|
# Unlike the Cura MeshBuilder, we use 4-vectors stored as columns for easier transform
|
||||||
self.verts = numpy.zeros((4, num_verts), dtype=numpy.float32)
|
self.verts = numpy.zeros((4, num_verts), dtype=numpy.float32)
|
||||||
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
|
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
|
||||||
self.num_verts = 0
|
self.num_verts = 0
|
||||||
self.reserveFaceCount(num_faces)
|
self.reserveFaceCount(num_faces)
|
||||||
|
|
||||||
def reserveFaceCount(self, num_faces):
|
def reserveFaceCount(self, num_faces):
|
||||||
self.faces = numpy.zeros((num_faces, 3), dtype=numpy.int32)
|
self.faces = numpy.zeros((num_faces, 3), dtype=numpy.int32)
|
||||||
self.num_faces = 0
|
self.num_faces = 0
|
||||||
|
|
||||||
def getVertexCount(self):
|
def getVertexCount(self):
|
||||||
return self.verts.shape[1]
|
return self.verts.shape[1]
|
||||||
|
|
||||||
def addVertex(self, x, y, z):
|
def addVertex(self, x, y, z):
|
||||||
self.verts[0, self.num_verts] = x
|
self.verts[0, self.num_verts] = x
|
||||||
self.verts[1, self.num_verts] = y
|
self.verts[1, self.num_verts] = y
|
||||||
self.verts[2, self.num_verts] = z
|
self.verts[2, self.num_verts] = z
|
||||||
self.num_verts += 1
|
self.num_verts += 1
|
||||||
|
|
||||||
# Indices are 0-based for this shape, but they won't be zero-based in the merged mesh
|
# Indices are 0-based for this shape, but they won't be zero-based in the merged mesh
|
||||||
def addTri(self, a, b, c):
|
def addTri(self, a, b, c):
|
||||||
self.faces[self.num_faces, 0] = self.index_base + a
|
self.faces[self.num_faces, 0] = self.index_base + a
|
||||||
self.faces[self.num_faces, 1] = self.index_base + b
|
self.faces[self.num_faces, 1] = self.index_base + b
|
||||||
self.faces[self.num_faces, 2] = self.index_base + c
|
self.faces[self.num_faces, 2] = self.index_base + c
|
||||||
self.num_faces += 1
|
self.num_faces += 1
|
||||||
|
|
||||||
def addTriFlip(self, a, b, c, ccw):
|
def addTriFlip(self, a, b, c, ccw):
|
||||||
if ccw:
|
if ccw:
|
||||||
self.addTri(a, b, c)
|
self.addTri(a, b, c)
|
||||||
else:
|
else:
|
||||||
self.addTri(b, a, c)
|
self.addTri(b, a, c)
|
||||||
|
|
||||||
# Needs to be convex, but not necessaily planar
|
# Needs to be convex, but not necessaily planar
|
||||||
# Assumed ccw, cut along the ac diagonal
|
# Assumed ccw, cut along the ac diagonal
|
||||||
def addQuad(self, a, b, c, d):
|
def addQuad(self, a, b, c, d):
|
||||||
self.addTri(a, b, c)
|
self.addTri(a, b, c)
|
||||||
self.addTri(c, d, a)
|
self.addTri(c, d, a)
|
||||||
|
|
||||||
def addQuadFlip(self, a, b, c, d, ccw):
|
def addQuadFlip(self, a, b, c, d, ccw):
|
||||||
if ccw:
|
if ccw:
|
||||||
self.addTri(a, b, c)
|
self.addTri(a, b, c)
|
||||||
self.addTri(c, d, a)
|
self.addTri(c, d, a)
|
||||||
else:
|
else:
|
||||||
self.addTri(a, c, b)
|
self.addTri(a, c, b)
|
||||||
self.addTri(c, a, d)
|
self.addTri(c, a, d)
|
||||||
|
|
||||||
|
|
||||||
# Arbitrary polygon triangulation.
|
# Arbitrary polygon triangulation.
|
||||||
# Doesn't assume convexity and doesn't check the "convex" flag in the file.
|
# Doesn't assume convexity and doesn't check the "convex" flag in the file.
|
||||||
# Works by the "cutting of ears" algorithm:
|
# Works by the "cutting of ears" algorithm:
|
||||||
|
@ -776,13 +777,13 @@ class X3DReader(MeshReader):
|
||||||
def addFace(self, indices, ccw):
|
def addFace(self, indices, ccw):
|
||||||
# Resolve indices to coordinates for faster math
|
# Resolve indices to coordinates for faster math
|
||||||
face = [Vector(data=self.verts[0:3, i]) for i in indices]
|
face = [Vector(data=self.verts[0:3, i]) for i in indices]
|
||||||
|
|
||||||
# Need a normal to the plane so that we can know which vertices form inner angles
|
# Need a normal to the plane so that we can know which vertices form inner angles
|
||||||
normal = findOuterNormal(face)
|
normal = findOuterNormal(face)
|
||||||
|
|
||||||
if not normal: # Couldn't find an outer edge, non-planar polygon maybe?
|
if not normal: # Couldn't find an outer edge, non-planar polygon maybe?
|
||||||
return
|
return
|
||||||
|
|
||||||
# Find the vertex with the smallest inner angle and no points inside, cut off. Repeat until done
|
# Find the vertex with the smallest inner angle and no points inside, cut off. Repeat until done
|
||||||
n = len(face)
|
n = len(face)
|
||||||
vi = [i for i in range(n)] # We'll be using this to kick vertices from the face
|
vi = [i for i in range(n)] # We'll be using this to kick vertices from the face
|
||||||
|
@ -807,17 +808,17 @@ class X3DReader(MeshReader):
|
||||||
if pointInsideTriangle(vx, next, prev, nextXprev):
|
if pointInsideTriangle(vx, next, prev, nextXprev):
|
||||||
no_points_inside = False
|
no_points_inside = False
|
||||||
break
|
break
|
||||||
|
|
||||||
if no_points_inside:
|
if no_points_inside:
|
||||||
max_cos = cos
|
max_cos = cos
|
||||||
i_min = i
|
i_min = i
|
||||||
|
|
||||||
self.addTriFlip(indices[vi[(i_min + n - 1) % n]], indices[vi[i_min]], indices[vi[(i_min + 1) % n]], ccw)
|
self.addTriFlip(indices[vi[(i_min + n - 1) % n]], indices[vi[i_min]], indices[vi[(i_min + 1) % n]], ccw)
|
||||||
vi.pop(i_min)
|
vi.pop(i_min)
|
||||||
n -= 1
|
n -= 1
|
||||||
self.addTriFlip(indices[vi[0]], indices[vi[1]], indices[vi[2]], ccw)
|
self.addTriFlip(indices[vi[0]], indices[vi[1]], indices[vi[2]], ccw)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# X3D field parsers
|
# X3D field parsers
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
@ -844,7 +845,7 @@ def readInt(node, attr, default):
|
||||||
if not s:
|
if not s:
|
||||||
return default
|
return default
|
||||||
return int(s, 0)
|
return int(s, 0)
|
||||||
|
|
||||||
def readBoolean(node, attr, default):
|
def readBoolean(node, attr, default):
|
||||||
s = node.attrib.get(attr)
|
s = node.attrib.get(attr)
|
||||||
if not s:
|
if not s:
|
||||||
|
@ -873,8 +874,8 @@ def readIndex(node, attr):
|
||||||
chunk.append(v[i])
|
chunk.append(v[i])
|
||||||
if chunk:
|
if chunk:
|
||||||
chunks.append(chunk)
|
chunks.append(chunk)
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
# Given a face as a sequence of vectors, returns a normal to the polygon place that forms a right triple
|
# Given a face as a sequence of vectors, returns a normal to the polygon place that forms a right triple
|
||||||
# with a vector along the polygon sequence and a vector backwards
|
# with a vector along the polygon sequence and a vector backwards
|
||||||
def findOuterNormal(face):
|
def findOuterNormal(face):
|
||||||
|
@ -894,25 +895,25 @@ def findOuterNormal(face):
|
||||||
if rejection.dot(prev_rejection) < -EPSILON: # points on both sides of the edge - not an outer one
|
if rejection.dot(prev_rejection) < -EPSILON: # points on both sides of the edge - not an outer one
|
||||||
is_outer = False
|
is_outer = False
|
||||||
break
|
break
|
||||||
elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability
|
elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability
|
||||||
prev_rejection = rejection
|
prev_rejection = rejection
|
||||||
|
|
||||||
if is_outer: # Found an outer edge, prev_rejection is the rejection inside the face. Generate a normal.
|
if is_outer: # Found an outer edge, prev_rejection is the rejection inside the face. Generate a normal.
|
||||||
return edge.cross(prev_rejection)
|
return edge.cross(prev_rejection)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
|
# Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
|
||||||
# No error handling.
|
# No error handling.
|
||||||
# For stability, taking the ration between the biggest coordinates would be better...
|
# For stability, taking the ration between the biggest coordinates would be better...
|
||||||
def ratio(a, b):
|
def ratio(a, b):
|
||||||
if b.x > EPSILON or b.x < -EPSILON:
|
if b.x > EPSILON or b.x < -EPSILON:
|
||||||
return a.x / b.x
|
return a.x / b.x
|
||||||
elif b.y > EPSILON or b.y < -EPSILON:
|
elif b.y > EPSILON or b.y < -EPSILON:
|
||||||
return a.y / b.y
|
return a.y / b.y
|
||||||
else:
|
else:
|
||||||
return a.z / b.z
|
return a.z / b.z
|
||||||
|
|
||||||
def pointInsideTriangle(vx, next, prev, nextXprev):
|
def pointInsideTriangle(vx, next, prev, nextXprev):
|
||||||
vxXprev = vx.cross(prev)
|
vxXprev = vx.cross(prev)
|
||||||
r = ratio(vxXprev, nextXprev)
|
r = ratio(vxXprev, nextXprev)
|
||||||
|
@ -921,4 +922,4 @@ def pointInsideTriangle(vx, next, prev, nextXprev):
|
||||||
vxXnext = vx.cross(next);
|
vxXnext = vx.cross(next);
|
||||||
s = -ratio(vxXnext, nextXprev)
|
s = -ratio(vxXnext, nextXprev)
|
||||||
return s > 0 and (s + r) < 1
|
return s > 0 and (s + r) < 1
|
||||||
|
|
||||||
|
|
|
@ -409,6 +409,7 @@ UM.MainWindow
|
||||||
{
|
{
|
||||||
id: objectsList;
|
id: objectsList;
|
||||||
visible: false;
|
visible: false;
|
||||||
|
//z: -10;
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: objectsButton.top;
|
top: objectsButton.top;
|
||||||
|
|
|
@ -55,7 +55,7 @@ Rectangle
|
||||||
//anchors.right: parent.right
|
//anchors.right: parent.right
|
||||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
||||||
text: Cura.ObjectManager.getItem(index).name;
|
text: Cura.ObjectManager.getItem(index).name;
|
||||||
color: Cura.ObjectManager.getItem(index).isSelected ? palette.highlightedText : palette.text
|
color: Cura.ObjectManager.getItem(index).isSelected ? palette.highlightedText : (Cura.ObjectManager.getItem(index).isOutsideBuildArea ? palette.mid : palette.text)
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ Rectangle
|
||||||
topMargin: UM.Theme.getSize("default_margin").height;
|
topMargin: UM.Theme.getSize("default_margin").height;
|
||||||
left: parent.left;
|
left: parent.left;
|
||||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||||
bottom: buildPlateSelection.top;
|
bottom: filterBuildPlateCheckbox.top;
|
||||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,25 @@ Rectangle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
id: filterBuildPlateCheckbox
|
||||||
|
checked: boolCheck(UM.Preferences.getValue("view/filter_current_build_plate"))
|
||||||
|
onClicked: UM.Preferences.setValue("view/filter_current_build_plate", checked)
|
||||||
|
|
||||||
|
text: catalog.i18nc("@option:check","Filter active build plate");
|
||||||
|
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: parent.left;
|
||||||
|
topMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
bottom: buildPlateSelection.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListModel
|
ListModel
|
||||||
{
|
{
|
||||||
id: buildPlatesModel
|
id: buildPlatesModel
|
||||||
|
|
|
@ -304,7 +304,7 @@ UM.PreferencesPage
|
||||||
text: catalog.i18nc("@option:check","Slice automatically");
|
text: catalog.i18nc("@option:check","Slice automatically");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
//: Spacer
|
//: Spacer
|
||||||
|
@ -451,6 +451,20 @@ UM.PreferencesPage
|
||||||
text: catalog.i18nc("@label","Opening and saving files")
|
text: catalog.i18nc("@label","Opening and saving files")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UM.TooltipArea {
|
||||||
|
width: childrenRect.width
|
||||||
|
height: childrenRect.height
|
||||||
|
text: catalog.i18nc("@info:tooltip","Should newly loaded models be arranged on the build palte?")
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
id: arrangeOnLoadCheckbox
|
||||||
|
text: catalog.i18nc("@option:check","Arrange objects on load")
|
||||||
|
checked: boolCheck(UM.Preferences.getValue("cura/arrange_objects_on_load"))
|
||||||
|
onCheckedChanged: UM.Preferences.setValue("cura/arrange_objects_on_load", checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UM.TooltipArea {
|
UM.TooltipArea {
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue