Skip to content

Commit

Permalink
Merge branch 'main' into PP-430-Enable-monotonic-raft-surface
Browse files Browse the repository at this point in the history
  • Loading branch information
casperlamboo authored Feb 27, 2024
2 parents 3eee666 + 2bdc70c commit e64b6ec
Show file tree
Hide file tree
Showing 783 changed files with 3,962 additions and 1,063 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/SlicingCrash.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ labels: ["Type: Bug", "Status: Triage", "Slicing Error :collision:"]
body:
- type: markdown
attributes:
value: |
value: |
### 💥 Slicing Crash Analysis Tool 💥
We are taking steps to analyze an increase in reported crashes more systematically. We'll need some help with that. 😇
Before filling out the report below, we want you to try a special Cura 5.7 Alpha.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
needs: [ conan-recipe-version ]
with:
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
conan_extra_args: '-g VirtualPythonEnv -o cura:devtools=True -c tools.build:skip_test=False'
conan_extra_args: '-g VirtualPythonEnv -o cura:devtools=True -c tools.build:skip_test=False --options "*:enable_sentry=False"'
unit_test_cmd: 'pytest --junitxml=junit_cura.xml'
unit_test_dir: 'tests'
conan_generator_dir: './venv/bin'
Expand Down
2 changes: 1 addition & 1 deletion UltiMaker-Cura.spec.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ app = BUNDLE(
}],
'CFBundleDocumentTypes': [{
'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['*'],
'CFBundleTypeExtensions': ['stl', 'obj', '3mf', 'gcode', 'ufp'],
'CFBundleTypeName': 'Model Files',
}]
},
Expand Down
8 changes: 8 additions & 0 deletions conandata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ pycharm_targets:
module_name: Cura
name: pytest in TestGCodeListDecorator.py
script_name: tests/TestGCodeListDecorator.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestHitChecker.py
script_name: tests/TestHitChecker.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestIntentManager.py
Expand Down Expand Up @@ -187,6 +191,10 @@ pycharm_targets:
module_name: Cura
name: pytest in TestPrintInformation.py
script_name: tests/TestPrintInformation.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestPrintOrderManager.py
script_name: tests/TestPrintOrderManager.py
- jinja_path: .run_templates/pycharm_cura_test.run.xml.jinja
module_name: Cura
name: pytest in TestProfileRequirements.py
Expand Down
1 change: 1 addition & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ def requirements(self):
self.requires("cpython/3.10.4@ultimaker/stable")
self.requires("clipper/6.4.2@ultimaker/stable")
self.requires("openssl/3.2.0")
self.requires("protobuf/3.21.12")
self.requires("boost/1.82.0")
self.requires("spdlog/1.12.0")
self.requires("fmt/10.1.1")
Expand Down
4 changes: 2 additions & 2 deletions cura/BuildVolume.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ def _computeDisallowedAreasPrinted(self, used_extruders):
result[extruder.getId()] = []

# Currently, the only normally printed object is the prime tower.
if self._global_container_stack.getProperty("prime_tower_enable", "value"):
if self._global_container_stack.getProperty("prime_tower_mode", "value") != 'none':
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
machine_width = self._global_container_stack.getProperty("machine_width", "value")
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
Expand Down Expand Up @@ -1208,7 +1208,7 @@ def _clamp(self, value, min_value, max_value):
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_layers", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "prime_blob_enable"]
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable", "prime_tower_base_size", "prime_tower_base_height"]
_tower_settings = ["prime_tower_mode", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable", "prime_tower_base_size", "prime_tower_base_height"]
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports", "wall_line_count", "wall_line_width_0", "wall_line_width_x"]
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "skirt_brim_extruder_nr", "raft_base_extruder_nr", "raft_interface_extruder_nr", "raft_surface_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
Expand Down
54 changes: 48 additions & 6 deletions cura/CuraApplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
from .Machines.Models.MachineListModel import MachineListModel
from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel
from .Machines.Models.IntentSelectionModel import IntentSelectionModel
from .PrintOrderManager import PrintOrderManager
from .SingleInstance import SingleInstance

if TYPE_CHECKING:
Expand Down Expand Up @@ -180,6 +181,7 @@ def __init__(self, *args, **kwargs):

# Variables set from CLI
self._files_to_open = []
self._urls_to_open = []
self._use_single_instance = False

self._single_instance = None
Expand All @@ -203,6 +205,7 @@ def __init__(self, *args, **kwargs):
self._container_manager = None

self._object_manager = None
self._print_order_manager = None
self._extruders_model = None
self._extruders_model_with_optional = None
self._build_plate_model = None
Expand Down Expand Up @@ -334,7 +337,7 @@ def parseCliOptions(self):
for filename in self._cli_args.file:
url = QUrl(filename)
if url.scheme() in self._supported_url_schemes:
self._open_url_queue.append(url)
self._urls_to_open.append(url)
else:
self._files_to_open.append(os.path.abspath(filename))

Expand All @@ -357,7 +360,7 @@ def initialize(self) -> None:
self._machine_action_manager.initialize()

def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
self._single_instance = SingleInstance(self, self._files_to_open, self._urls_to_open)

# If we use single instance, try to connect to the single instance server, send commands, and then exit.
# If we cannot find an existing single instance server, this is the only instance, so just keep going.
Expand Down Expand Up @@ -598,7 +601,9 @@ def startSplashWindowPhase(self) -> None:
preferences.addPreference("mesh/scale_to_fit", False)
preferences.addPreference("mesh/scale_tiny_meshes", True)
preferences.addPreference("cura/dialog_on_project_save", True)
preferences.addPreference("cura/dialog_on_ucp_project_save", True)
preferences.addPreference("cura/asked_dialog_on_project_save", False)
preferences.addPreference("cura/asked_dialog_on_ucp_project_save", False)
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/use_multi_build_plate", False)
Expand All @@ -614,6 +619,7 @@ def startSplashWindowPhase(self) -> None:

preferences.addPreference("view/invert_zoom", False)
preferences.addPreference("view/filter_current_build_plate", False)
preferences.addPreference("view/navigation_style", "cura")
preferences.addPreference("cura/sidebar_collapsed", False)

preferences.addPreference("cura/favorite_materials", "")
Expand Down Expand Up @@ -906,6 +912,7 @@ def run(self):
# initialize info objects
self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self)
self._print_order_manager = PrintOrderManager(self.getObjectsModel().getNodes)
self.processEvents()
# Initialize setting visibility presets model.
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
Expand Down Expand Up @@ -963,6 +970,8 @@ def _onPostStart(self):
self.callLater(self._openFile, file_name)
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
self.callLater(self._openFile, file_name)
for url in self._urls_to_open:
self.callLater(self._openUrl, url)
for url in self._open_url_queue:
self.callLater(self._openUrl, url)

Expand All @@ -986,6 +995,7 @@ def runWithGUI(self):
t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

Selection.selectionChanged.connect(self.onSelectionChanged)
self._print_order_manager.printOrderChanged.connect(self._onPrintOrderChanged)

# Set default background color for scene
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
Expand Down Expand Up @@ -1075,6 +1085,10 @@ def getMachineSettingsManager(self, *args) -> "MachineSettingsManager":
def getTextManager(self, *args) -> "TextManager":
return self._text_manager

@pyqtSlot(bool)
def getWorkplaceDropToBuildplate(self, drop_to_build_plate: bool) ->None:
return self._physics.setAppPerModelDropDown(drop_to_build_plate)

def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions":
if self._cura_formula_functions is None:
self._cura_formula_functions = CuraFormulaFunctions(self)
Expand All @@ -1101,6 +1115,10 @@ def getObjectsModel(self, *args):
self._object_manager = ObjectsModel(self)
return self._object_manager

@pyqtSlot(str, result = "QVariantList")
def getSupportedActionMachineList(self, definition_id: str) -> List["MachineAction"]:
return self._machine_action_manager.getSupportedActions(self._machine_manager.getDefinitionByMachineId(definition_id))

@pyqtSlot(result = QObject)
def getExtrudersModel(self, *args) -> "ExtrudersModel":
if self._extruders_model is None:
Expand All @@ -1126,6 +1144,16 @@ def getBuildPlateModel(self, *args) -> BuildPlateModel:
self._build_plate_model = BuildPlateModel(self)
return self._build_plate_model

@pyqtSlot()
def exportUcp(self):
writer = self.getMeshFileHandler().getWriter("3MFWriter")

if writer is None:
Logger.warning("3mf writer is not enabled")
return

writer.exportUcp()

def getCuraSceneController(self, *args) -> CuraSceneController:
if self._cura_scene_controller is None:
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
Expand Down Expand Up @@ -1255,6 +1283,7 @@ def registerObjects(self, engine):
self.processEvents()
engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self)
engine.rootContext().setContextProperty("PrintOrderManager", self._print_order_manager)
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
Expand Down Expand Up @@ -1750,30 +1779,43 @@ def groupSelected(self) -> None:
Selection.remove(node)
Selection.add(group_node)

all_nodes = self.getObjectsModel().getNodes()
PrintOrderManager.updatePrintOrdersAfterGroupOperation(all_nodes, group_node, selected_nodes)

@pyqtSlot()
def ungroupSelected(self) -> None:
all_nodes = self.getObjectsModel().getNodes()
selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects:
if node.callDecoration("isGroup"):
op = GroupedOperation()

group_parent = node.getParent()
children = node.getChildren().copy()
for child in children:
# Ungroup only 1 level deep
if child.getParent() != node:
continue

# Ungroup only 1 level deep
children_to_ungroup = list(filter(lambda child: child.getParent() == node, children))
for child in children_to_ungroup:
# Set the parent of the children to the parent of the group-node
op.addOperation(SetParentOperation(child, group_parent))

# Add all individual nodes to the selection
Selection.add(child)

PrintOrderManager.updatePrintOrdersAfterUngroupOperation(all_nodes, node, children_to_ungroup)
op.push()
# Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged

def _onPrintOrderChanged(self) -> None:
# update object list
scene = self.getController().getScene()
scene.sceneChanged.emit(scene.getRoot())

# reset if already was sliced
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()

def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
if self._is_headless:
return None
Expand Down
88 changes: 88 additions & 0 deletions cura/HitChecker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from typing import List, Dict
from cura.Scene.CuraSceneNode import CuraSceneNode


class HitChecker:
"""Checks if nodes can be printed without causing any collisions and interference"""

def __init__(self, nodes: List[CuraSceneNode]) -> None:
self._hit_map = self._buildHitMap(nodes)

def anyTwoNodesBlockEachOther(self, nodes: List[CuraSceneNode]) -> bool:
"""Returns True if any 2 nodes block each other"""
for a in nodes:
for b in nodes:
if self._hit_map[a][b] and self._hit_map[b][a]:
return True
return False

def canPrintBefore(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
"""Returns True if node doesn't block other_nodes and can be printed before them"""
no_hits = all(not self._hit_map[node][other_node] for other_node in other_nodes)
return no_hits

def canPrintAfter(self, node: CuraSceneNode, other_nodes: List[CuraSceneNode]) -> bool:
"""Returns True if node doesn't hit other nodes and can be printed after them"""
no_hits = all(not self._hit_map[other_node][node] for other_node in other_nodes)
return no_hits

def calculateScore(self, a: CuraSceneNode, b: CuraSceneNode) -> int:
"""Calculate score simply sums the number of other objects it 'blocks'
:param a: node
:param b: node
:return: sum of the number of other objects
"""

score_a = sum(self._hit_map[a].values())
score_b = sum(self._hit_map[b].values())
return score_a - score_b

def canPrintNodesInProvidedOrder(self, ordered_nodes: List[CuraSceneNode]) -> bool:
"""Returns True If nodes don't have any hits in provided order"""
for node_index, node in enumerate(ordered_nodes):
nodes_before = ordered_nodes[:node_index - 1] if node_index - 1 >= 0 else []
nodes_after = ordered_nodes[node_index + 1:] if node_index + 1 < len(ordered_nodes) else []
if not self.canPrintBefore(node, nodes_after) or not self.canPrintAfter(node, nodes_before):
return False
return True

@staticmethod
def _buildHitMap(nodes: List[CuraSceneNode]) -> Dict[CuraSceneNode, CuraSceneNode]:
"""Pre-computes all hits between all objects
:nodes: nodes that need to be checked for collisions
:return: dictionary where hit_map[node1][node2] is False if there node1 can be printed before node2
"""
hit_map = {j: {i: HitChecker._checkHit(j, i) for i in nodes} for j in nodes}
return hit_map

@staticmethod
def _checkHit(a: CuraSceneNode, b: CuraSceneNode) -> bool:
"""Checks if a can be printed before b
:param a: node
:param b: node
:return: False if a can be printed before b
"""

if a == b:
return False

a_hit_hull = a.callDecoration("getConvexHullBoundary")
b_hit_hull = b.callDecoration("getConvexHullHeadFull")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)

if overlap:
return True

# Adhesion areas must never overlap, regardless of printing order
# This would cause over-extrusion
a_hit_hull = a.callDecoration("getAdhesionArea")
b_hit_hull = b.callDecoration("getAdhesionArea")
overlap = a_hit_hull.intersectsPolygon(b_hit_hull)

if overlap:
return True
else:
return False
11 changes: 8 additions & 3 deletions cura/OAuth2/AuthorizationHelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

catalog = i18nCatalog("cura")
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
REQUEST_TIMEOUT = 5 # Seconds


class AuthorizationHelpers:
Expand Down Expand Up @@ -53,7 +54,8 @@ def getAccessTokenUsingAuthorizationCode(self, authorization_code: str, verifica
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
timeout = REQUEST_TIMEOUT
)

def getAccessTokenUsingRefreshToken(self, refresh_token: str, callback: Callable[[AuthenticationResponse], None]) -> None:
Expand All @@ -77,7 +79,9 @@ def getAccessTokenUsingRefreshToken(self, refresh_token: str, callback: Callable
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
urgent = True,
timeout = REQUEST_TIMEOUT
)

def parseTokenResponse(self, token_response: QNetworkReply, callback: Callable[[AuthenticationResponse], None]) -> None:
Expand Down Expand Up @@ -122,7 +126,8 @@ def checkToken(self, access_token: str, success_callback: Optional[Callable[[Use
check_token_url,
headers_dict = headers,
callback = lambda reply: self._parseUserProfile(reply, success_callback, failed_callback),
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None,
timeout = REQUEST_TIMEOUT
)

def _parseUserProfile(self, reply: QNetworkReply, success_callback: Optional[Callable[[UserProfile], None]], failed_callback: Optional[Callable[[], None]] = None) -> None:
Expand Down
Loading

0 comments on commit e64b6ec

Please sign in to comment.