Merge branch 'master' into feature_intent_per_quality

This commit is contained in:
Remco Burema 2019-07-02 10:21:20 +02:00
commit fee6ec417f
5 changed files with 252 additions and 74 deletions

View File

@ -727,48 +727,17 @@ class BuildVolume(SceneNode):
self._error_areas = [] self._error_areas = []
extruder_manager = ExtruderManager.getInstance() used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
used_extruders = extruder_manager.getUsedExtruderStacks()
disallowed_border_size = self.getEdgeDisallowedSize() disallowed_border_size = self.getEdgeDisallowedSize()
if not used_extruders: result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) # Normal machine disallowed areas can always be added.
# If no extruder is used, assume that the active extruder is used (else nothing is drawn)
if extruder_manager.getActiveExtruderStack():
used_extruders = [extruder_manager.getActiveExtruderStack()]
else:
used_extruders = [self._global_container_stack]
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders) prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
result_areas_no_brim = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking. result_areas_no_brim = self._computeDisallowedAreasStatic(0, used_extruders) # Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
prime_disallowed_areas = copy.deepcopy(result_areas_no_brim)
#Check if prime positions intersect with disallowed areas. # Check if prime positions intersect with disallowed areas.
for extruder in used_extruders: for extruder in used_extruders:
extruder_id = extruder.getId() extruder_id = extruder.getId()
collision = False
for prime_polygon in prime_areas[extruder_id]:
for disallowed_polygon in prime_disallowed_areas[extruder_id]:
if prime_polygon.intersectsPolygon(disallowed_polygon) is not None:
collision = True
break
if collision:
break
#Also check other prime positions (without additional offset).
for other_extruder_id in prime_areas:
if extruder_id == other_extruder_id: #It is allowed to collide with itself.
continue
for other_prime_polygon in prime_areas[other_extruder_id]:
if prime_polygon.intersectsPolygon(other_prime_polygon):
collision = True
break
if collision:
break
if collision:
break
result_areas[extruder_id].extend(prime_areas[extruder_id]) result_areas[extruder_id].extend(prime_areas[extruder_id])
result_areas_no_brim[extruder_id].extend(prime_areas[extruder_id]) result_areas_no_brim[extruder_id].extend(prime_areas[extruder_id])
@ -776,37 +745,32 @@ class BuildVolume(SceneNode):
for area in nozzle_disallowed_areas: for area in nozzle_disallowed_areas:
polygon = Polygon(numpy.array(area, numpy.float32)) polygon = Polygon(numpy.array(area, numpy.float32))
polygon_disallowed_border = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size)) polygon_disallowed_border = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
result_areas[extruder_id].append(polygon_disallowed_border) #Don't perform the offset on these. result_areas[extruder_id].append(polygon_disallowed_border) # Don't perform the offset on these.
#polygon_minimal_border = polygon.getMinkowskiHull(5) result_areas_no_brim[extruder_id].append(polygon) # No brim
result_areas_no_brim[extruder_id].append(polygon) # no brim
# Add prime tower location as disallowed area. # Add prime tower location as disallowed area.
if len(used_extruders) > 1: #No prime tower in single-extrusion. if len([x for x in used_extruders if x.isEnabled]) > 1: # No prime tower if only one extruder is enabled
prime_tower_collision = False
if len([x for x in used_extruders if x.isEnabled]) > 1: #No prime tower if only one extruder is enabled prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
prime_tower_collision = False for extruder_id in prime_tower_areas:
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) for area_index, prime_tower_area in enumerate(prime_tower_areas[extruder_id]):
for extruder_id in prime_tower_areas: for area in result_areas[extruder_id]:
for i_area, prime_tower_area in enumerate(prime_tower_areas[extruder_id]): if prime_tower_area.intersectsPolygon(area) is not None:
for area in result_areas[extruder_id]: prime_tower_collision = True
if prime_tower_area.intersectsPolygon(area) is not None:
prime_tower_collision = True
break
if prime_tower_collision: #Already found a collision.
break break
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and if prime_tower_collision: # Already found a collision.
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"): break
prime_tower_areas[extruder_id][i_area] = prime_tower_area.getMinkowskiHull( if self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
Polygon.approximatedCircle(disallowed_border_size)) prime_tower_areas[extruder_id][area_index] = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
if not prime_tower_collision: if not prime_tower_collision:
result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id]) result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
else: else:
self._error_areas.extend(prime_tower_areas[extruder_id]) self._error_areas.extend(prime_tower_areas[extruder_id])
self._has_errors = len(self._error_areas) > 0 self._has_errors = len(self._error_areas) > 0
self._disallowed_areas = [] self._disallowed_areas = [] # type: List[Polygon]
for extruder_id in result_areas: for extruder_id in result_areas:
self._disallowed_areas.extend(result_areas[extruder_id]) self._disallowed_areas.extend(result_areas[extruder_id])
self._disallowed_areas_no_brim = [] self._disallowed_areas_no_brim = []
@ -826,8 +790,8 @@ class BuildVolume(SceneNode):
for extruder in used_extruders: for extruder in used_extruders:
result[extruder.getId()] = [] result[extruder.getId()] = []
#Currently, the only normally printed object is the prime tower. # Currently, the only normally printed object is the prime tower.
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable"): if self._global_container_stack.getProperty("prime_tower_enable"):
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_width = self._global_container_stack.getProperty("machine_width", "value")
machine_depth = self._global_container_stack.getProperty("machine_depth", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
@ -837,8 +801,7 @@ class BuildVolume(SceneNode):
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left. prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
prime_tower_y = prime_tower_y + machine_depth / 2 prime_tower_y = prime_tower_y + machine_depth / 2
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and if self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
brim_size = ( brim_size = (
extruder.getProperty("brim_line_count", "value") * extruder.getProperty("brim_line_count", "value") *
extruder.getProperty("skirt_brim_line_width", "value") / 100.0 * extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
@ -908,9 +871,12 @@ class BuildVolume(SceneNode):
# for. # for.
# \return A dictionary with for each used extruder ID the disallowed areas # \return A dictionary with for each used extruder ID the disallowed areas
# where that extruder may not print. # where that extruder may not print.
def _computeDisallowedAreasStatic(self, border_size, used_extruders): def _computeDisallowedAreasStatic(self, border_size:float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
#Convert disallowed areas to polygons and dilate them. # Convert disallowed areas to polygons and dilate them.
machine_disallowed_polygons = [] machine_disallowed_polygons = []
if self._global_container_stack is None:
return {}
for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"): for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"):
polygon = Polygon(numpy.array(area, numpy.float32)) polygon = Polygon(numpy.array(area, numpy.float32))
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
@ -921,7 +887,7 @@ class BuildVolume(SceneNode):
nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry( nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry(
"nozzle_offsetting_for_disallowed_areas", True) "nozzle_offsetting_for_disallowed_areas", True)
result = {} result = {} # type: Dict[str, List[Polygon]]
for extruder in used_extruders: for extruder in used_extruders:
extruder_id = extruder.getId() extruder_id = extruder.getId()
offset_x = extruder.getProperty("machine_nozzle_offset_x", "value") offset_x = extruder.getProperty("machine_nozzle_offset_x", "value")
@ -930,13 +896,13 @@ class BuildVolume(SceneNode):
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value") offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
if offset_y is None: if offset_y is None:
offset_y = 0 offset_y = 0
offset_y = -offset_y #Y direction of g-code is the inverse of Y direction of Cura's scene space. offset_y = -offset_y # Y direction of g-code is the inverse of Y direction of Cura's scene space.
result[extruder_id] = [] result[extruder_id] = []
for polygon in machine_disallowed_polygons: for polygon in machine_disallowed_polygons:
result[extruder_id].append(polygon.translate(offset_x, offset_y)) #Compensate for the nozzle offset of this extruder. result[extruder_id].append(polygon.translate(offset_x, offset_y)) # Compensate for the nozzle offset of this extruder.
#Add the border around the edge of the build volume. # Add the border around the edge of the build volume.
left_unreachable_border = 0 left_unreachable_border = 0
right_unreachable_border = 0 right_unreachable_border = 0
top_unreachable_border = 0 top_unreachable_border = 0
@ -944,7 +910,8 @@ class BuildVolume(SceneNode):
# Only do nozzle offsetting if needed # Only do nozzle offsetting if needed
if nozzle_offsetting_for_disallowed_areas: if nozzle_offsetting_for_disallowed_areas:
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders. # The build volume is defined as the union of the area that all extruders can reach, so we need to know
# the relative offset to all extruders.
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value") other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
if other_offset_x is None: if other_offset_x is None:
@ -1028,8 +995,8 @@ class BuildVolume(SceneNode):
[ half_machine_width - border_size, 0] [ half_machine_width - border_size, 0]
], numpy.float32))) ], numpy.float32)))
result[extruder_id].append(Polygon(numpy.array([ result[extruder_id].append(Polygon(numpy.array([
[ half_machine_width,-half_machine_depth], [ half_machine_width, -half_machine_depth],
[-half_machine_width,-half_machine_depth], [-half_machine_width, -half_machine_depth],
[ 0, -half_machine_depth + border_size] [ 0, -half_machine_depth + border_size]
], numpy.float32))) ], numpy.float32)))

View File

@ -205,7 +205,7 @@ class ExtruderManager(QObject):
# list. # list.
# #
# \return A list of extruder stacks. # \return A list of extruder stacks.
def getUsedExtruderStacks(self) -> List["ContainerStack"]: def getUsedExtruderStacks(self) -> List["ExtruderStack"]:
global_stack = self._application.getGlobalContainerStack() global_stack = self._application.getGlobalContainerStack()
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()

View File

@ -1196,6 +1196,13 @@ class XmlMaterialProfile(InstanceContainer):
"surface energy": "material_surface_energy", "surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage", "shrinkage percentage": "material_shrinkage_percentage",
"build volume temperature": "build_volume_temperature", "build volume temperature": "build_volume_temperature",
"anti ooze retracted position": "material_anti_ooze_retracted_position",
"anti ooze retract speed": "material_anti_ooze_retraction_speed",
"break preparation retracted position": "material_break_preparation_retracted_position",
"break preparation speed": "material_break_preparation_speed",
"break retracted position": "material_break_retracted_position",
"break speed": "material_break_speed",
"break temperature": "material_break_temperature"
} }
__unmapped_settings = [ __unmapped_settings = [
"hardware compatible", "hardware compatible",

View File

@ -2234,6 +2234,107 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
"material_crystallinity":
{
"label": "Crystalline Material",
"description": "Is this material the type that breaks off cleanly when heated (crystalline), or is it the type that produces long intertwined polymer chains (non-crystalline)?",
"type": "bool",
"default_value": false,
"enabled": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_anti_ooze_retracted_position":
{
"label": "Anti-ooze Retracted Position",
"description": "How far the material needs to be retracted before it stops oozing.",
"type": "float",
"unit": "mm",
"default_value": 4,
"enabled": false,
"minimum_value_warning": "0",
"maximum_value_warning": "retraction_amount",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_anti_ooze_retraction_speed":
{
"label": "Anti-ooze Retraction Speed",
"description": "How fast the material needs to be retracted during a filament switch to prevent oozing.",
"type": "float",
"unit": "mm/s",
"default_value": 5,
"enabled": false,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_break_preparation_retracted_position":
{
"label": "Break Preparation Retracted Position",
"description": "How far the filament can be stretched before it breaks, while heated.",
"type": "float",
"unit": "mm",
"default_value": 16,
"enabled": false,
"minimum_value_warning": "0",
"maximum_value_warning": "retraction_amount * 4",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_break_preparation_speed":
{
"label": "Break Preparation Retraction Speed",
"description": "How fast the filament needs to be retracted just before breaking it off in a retraction.",
"type": "float",
"unit": "mm/s",
"default_value": 2,
"enabled": false,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_break_retracted_position":
{
"label": "Break Retracted Position",
"description": "How far to retract the filament in order to break it cleanly.",
"type": "float",
"unit": "mm",
"default_value": 50,
"enabled": false,
"minimum_value_warning": "0",
"maximum_value_warning": "100",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_break_speed":
{
"label": "Break Retraction Speed",
"description": "The speed at which to retract the filament in order to break it cleanly.",
"type": "float",
"unit": "mm/s",
"default_value": 25,
"enabled": false,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_break_temperature":
{
"label": "Break Temperature",
"description": "The temperature at which the filament is broken for a clean break.",
"type": "float",
"unit": "°C",
"default_value": 50,
"enabled": false,
"minimum_value": "-273.15",
"maximum_value_warning": "300",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_flow": "material_flow":
{ {
"label": "Flow", "label": "Flow",

View File

@ -43,6 +43,109 @@ def test_buildGridMesh(build_volume):
assert numpy.array_equal(result_vertices, mesh.getVertices()) assert numpy.array_equal(result_vertices, mesh.getVertices())
def test_clamp(build_volume):
assert build_volume._clamp(0, 0, 200) == 0
assert build_volume._clamp(0, -200, 200) == 0
assert build_volume._clamp(300, -200, 200) == 200
class TestCalculateBedAdhesionSize:
setting_property_dict = {"adhesion_type": {"value": "brim"},
"skirt_brim_line_width": {"value": 0},
"initial_layer_line_width_factor": {"value": 0},
"brim_line_count": {"value": 0},
"machine_width": {"value": 200},
"machine_depth": {"value": 200},
"skirt_line_count": {"value": 0},
"skirt_gap": {"value": 0},
"raft_margin": {"value": 0}
}
def getPropertySideEffect(*args, **kwargs):
properties = TestCalculateBedAdhesionSize.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def createAndSetGlobalStack(self, build_volume):
mocked_stack = MagicMock()
mocked_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_stack
def test_noGlobalStack(self, build_volume: BuildVolume):
assert build_volume._calculateBedAdhesionSize([]) is None
@pytest.mark.parametrize("setting_dict, result", [
({}, 0),
({"adhesion_type": {"value": "skirt"}}, 0),
({"adhesion_type": {"value": "raft"}}, 0),
({"adhesion_type": {"value": "none"}}, 0),
({"adhesion_type": {"value": "skirt"}, "skirt_line_count": {"value": 2}, "initial_layer_line_width_factor": {"value": 1}, "skirt_brim_line_width": {"value": 2}}, 0.02),
# Even though it's marked as skirt, it should behave as a brim as the prime tower has a brim (skirt line count is still at 0!)
({"adhesion_type": {"value": "skirt"}, "prime_tower_brim_enable": {"value": True}, "skirt_brim_line_width": {"value": 2}, "initial_layer_line_width_factor": {"value": 3}}, -0.06),
({"brim_line_count": {"value": 1}, "skirt_brim_line_width": {"value": 2}, "initial_layer_line_width_factor": {"value": 3}}, 0),
({"brim_line_count": {"value": 2}, "skirt_brim_line_width": {"value": 2}, "initial_layer_line_width_factor": {"value": 3}}, 0.06),
({"brim_line_count": {"value": 9000000}, "skirt_brim_line_width": {"value": 90000}, "initial_layer_line_width_factor": {"value": 9000}}, 100), # Clamped at half the max size of buildplate
])
def test_singleExtruder(self, build_volume: BuildVolume, setting_dict, result):
self.createAndSetGlobalStack(build_volume)
patched_dictionary = self.setting_property_dict.copy()
patched_dictionary.update(setting_dict)
with patch.dict(self.setting_property_dict, patched_dictionary):
assert build_volume._calculateBedAdhesionSize([]) == result
def test_unknownBedAdhesion(self, build_volume: BuildVolume):
self.createAndSetGlobalStack(build_volume)
patched_dictionary = self.setting_property_dict.copy()
patched_dictionary.update({"adhesion_type": {"value": "OMGZOMGBBQ"}})
with patch.dict(self.setting_property_dict, patched_dictionary):
with pytest.raises(Exception):
build_volume._calculateBedAdhesionSize([])
class TestComputeDisallowedAreasStatic:
setting_property_dict = {"machine_disallowed_areas": {"value": [[[-200, 112.5], [ -82, 112.5], [ -84, 102.5], [-115, 102.5]]]},
"machine_width": {"value": 200},
"machine_depth": {"value": 200},
}
def getPropertySideEffect(*args, **kwargs):
properties = TestComputeDisallowedAreasStatic.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def test_computeDisallowedAreasStaticNoExtruder(self, build_volume: BuildVolume):
mocked_stack = MagicMock()
mocked_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_stack
assert build_volume._computeDisallowedAreasStatic(0, []) == {}
def test_computeDisalowedAreasStaticSingleExtruder(self, build_volume: BuildVolume):
mocked_stack = MagicMock()
mocked_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
mocked_extruder = MagicMock()
mocked_extruder.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
mocked_extruder.getId = MagicMock(return_value = "zomg")
build_volume._global_container_stack = mocked_stack
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"):
result = build_volume._computeDisallowedAreasStatic(0, [mocked_extruder])
assert result == {"zomg": [Polygon([[-84.0, 102.5], [-115.0, 102.5], [-200.0, 112.5], [-82.0, 112.5]])]}
def test_computeDisalowedAreasMutliExtruder(self, build_volume):
mocked_stack = MagicMock()
mocked_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
mocked_extruder = MagicMock()
mocked_extruder.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
mocked_extruder.getId = MagicMock(return_value="zomg")
extruder_manager = MagicMock()
extruder_manager.getActiveExtruderStacks = MagicMock(return_value = [mocked_stack])
build_volume._global_container_stack = mocked_stack
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance", MagicMock(return_value = extruder_manager)):
result = build_volume._computeDisallowedAreasStatic(0, [mocked_extruder])
assert result == {"zomg": [Polygon([[-84.0, 102.5], [-115.0, 102.5], [-200.0, 112.5], [-82.0, 112.5]])]}
class TestUpdateRaftThickness: class TestUpdateRaftThickness:
setting_property_dict = {"raft_base_thickness": {"value": 1}, setting_property_dict = {"raft_base_thickness": {"value": 1},