Merge branch 'master' into remove-package-check

This commit is contained in:
Jack Ha 2018-07-09 11:25:19 +02:00
commit bff9a3afb5
172 changed files with 557 additions and 518 deletions

View file

@ -41,39 +41,32 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcode
class GcodeStartEndFormatter(Formatter):
def get_value(self, key: str, *args: str, **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
def get_value(self, key: str, *args: str, default_extruder_nr: str = "-1", **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
# and a default_extruder_nr to use when no extruder_nr is specified
if isinstance(key, str):
extruder_nr = int(default_extruder_nr)
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
try:
extruder_nr = int(kwargs["default_extruder_nr"])
extruder_nr = int(key_fragments[1])
except ValueError:
extruder_nr = -1
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
try:
extruder_nr = int(key_fragments[1])
except ValueError:
try:
extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack
except (KeyError, ValueError):
# either the key does not exist, or the value is not an int
Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0])
elif len(key_fragments) != 1:
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
return "{" + str(key) + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
else:
extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack #TODO: How can you ever provide the '-1' kwarg?
except (KeyError, ValueError):
# either the key does not exist, or the value is not an int
Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0])
elif len(key_fragments) != 1:
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
return "{" + str(key) + "}"
return "{" + key + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
## Job class that builds up the message of scene data to send to CuraEngine.

View file

@ -286,6 +286,9 @@ class FlavorParser:
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
return None
self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")
scene_node = CuraSceneNode()

View file

@ -105,9 +105,12 @@ class Script:
if m is None:
return default
try:
return float(m.group(0))
except:
return default
return int(m.group(0))
except ValueError: #Not an integer.
try:
return float(m.group(0))
except ValueError: #Not a number at all.
return default
## Convenience function to produce a line of g-code.
#

View file

@ -1,5 +1,9 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from ..Script import Script
# from cura.Settings.ExtruderManager import ExtruderManager
from UM.Application import Application #To get the current printer's settings.
class PauseAtHeight(Script):
def __init__(self):
@ -136,6 +140,10 @@ class PauseAtHeight(Script):
layers_started = False
redo_layers = self.getSettingValueByKey("redo_layers")
standby_temperature = self.getSettingValueByKey("standby_temperature")
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
is_griffin = False
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
@ -153,6 +161,8 @@ class PauseAtHeight(Script):
# Scroll each line of instruction for each layer in the G-code
for line in lines:
if ";FLAVOR:Griffin" in line:
is_griffin = True
# Fist positive layer reached
if ";LAYER:0" in line:
layers_started = True
@ -162,12 +172,12 @@ class PauseAtHeight(Script):
#Track the latest printing temperature in order to resume at the correct temperature.
if line.startswith("T"):
current_t = int(self.getValue(line, "T"))
current_t = self.getValue(line, "T")
m = self.getValue(line, "M")
if m is not None and (int(m) == 104 or int(m) == 109) and self.getValue(line, "S") is not None:
if m is not None and (m == 104 or m == 109) and self.getValue(line, "S") is not None:
extruder = current_t
if self.getValue(line, "T") is not None:
extruder = int(self.getValue(line, "T"))
extruder = self.getValue(line, "T")
target_temperature[extruder] = self.getValue(line, "S")
if not layers_started:
@ -247,57 +257,67 @@ class PauseAtHeight(Script):
prepend_gcode += ";added code by post processing\n"
prepend_gcode += ";script: PauseAtHeight.py\n"
if pause_at == "height":
prepend_gcode += ";current z: {z}\n".format(z=current_z)
prepend_gcode += ";current height: {height}\n".format(height=current_height)
prepend_gcode += ";current z: {z}\n".format(z = current_z)
prepend_gcode += ";current height: {height}\n".format(height = current_height)
else:
prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer)
prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer)
# Retraction
prepend_gcode += self.putValue(M=83) + "\n"
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
if not is_griffin:
# Retraction
prepend_gcode += self.putValue(M = 83) + "\n"
if retraction_amount != 0:
if firmware_retract:
prepend_gcode += self.putValue(G = 10)
else:
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head away
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n"
# Move the head away
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
# This line should be ok
prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n"
# This line should be ok
prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
# Set extruder standby temperature
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n"
if control_temperatures:
# Set extruder standby temperature
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
# Wait till the user continues printing
prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n"
prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n"
# Set extruder resume temperature
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n"
if not is_griffin:
if control_temperatures:
# Set extruder resume temperature
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n"
# Push the filament back,
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n"
# Push the filament back,
if retraction_amount != 0:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n"
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
# and retract again, the properly primes the nozzle
# when changing filament.
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
# and retract again, the properly primes the nozzle
# when changing filament.
if retraction_amount != 0:
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head back
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n"
prepend_gcode += self.putValue(G=1, X=x, Y=y, F=9000) + "\n"
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n"
prepend_gcode += self.putValue(G=1, F=9000) + "\n"
prepend_gcode += self.putValue(M=82) + "\n"
# Move the head back
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
if retraction_amount != 0:
if firmware_retract:
prepend_gcode += self.putValue(G = 11)
else:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
prepend_gcode += self.putValue(M = 82) + "\n"
# reset extrude value to pre pause value
prepend_gcode += self.putValue(G=92, E=current_e) + "\n"
# reset extrude value to pre pause value
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
layer = prepend_gcode + layer

View file

@ -76,11 +76,26 @@ Item
}
}
Component
{
id: columnTextDelegate
Label
{
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: styleData.value || ""
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}
}
TableViewColumn
{
role: "machine"
title: "Machine"
width: Math.floor(table.width * 0.25)
delegate: columnTextDelegate
}
TableViewColumn
{

View file

@ -74,6 +74,7 @@ Item
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
width: childrenRect.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@label", "Version") + ":"
@ -110,6 +111,7 @@ Item
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
height: childrenRect.height
Label
{
text: details.version || catalog.i18nc("@label", "Unknown")

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@ -9,10 +9,10 @@ import UM 1.1 as UM
Column
{
height: childrenRect.height
height: childrenRect.height + 2 * padding
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height
Label
{
id: heading
@ -25,7 +25,7 @@ Column
{
id: grid
property var model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
width: parent.width
width: parent.width - 2 * parent.padding
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").height
rowSpacing: UM.Theme.getSize("default_margin").width

View file

@ -15,22 +15,15 @@ ScrollView
flickableItem.flickableDirection: Flickable.VerticalFlick
Column
{
width: parent.width - 2 * padding
width: base.width
spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height
height: childrenRect.height + 2 * padding
height: childrenRect.height
ToolboxDownloadsShowcase
{
id: showcase
width: parent.width
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
}
ToolboxDownloadsGrid
{

View file

@ -1,46 +1,53 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Column
Rectangle
{
color: UM.Theme.getColor("secondary")
height: childrenRect.height
spacing: UM.Theme.getSize("toolbox_showcase_spacing").width
width: parent.width
Label
Column
{
id: heading
text: catalog.i18nc("@label", "Featured")
height: childrenRect.height + 2 * padding
spacing: UM.Theme.getSize("toolbox_showcase_spacing").width
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Grid
{
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
columns: 3
anchors
padding: UM.Theme.getSize("wide_margin").height
Label
{
horizontalCenter: parent.horizontalCenter
id: heading
text: catalog.i18nc("@label", "Featured")
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Repeater
Grid
{
model: {
if ( toolbox.viewCategory == "plugin" )
{
return toolbox.pluginsShowcaseModel
}
if ( toolbox.viewCategory == "material" )
{
return toolbox.materialsShowcaseModel
}
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
columns: 3
anchors
{
horizontalCenter: parent.horizontalCenter
}
Repeater
{
model: {
if ( toolbox.viewCategory == "plugin" )
{
return toolbox.pluginsShowcaseModel
}
if ( toolbox.viewCategory == "material" )
{
return toolbox.materialsShowcaseModel
}
}
delegate: ToolboxDownloadsShowcaseTile {}
}
delegate: ToolboxDownloadsShowcaseTile {}
}
}
}

View file

@ -1,37 +1,31 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import UM 1.1 as UM
Item
Rectangle
{
width: UM.Theme.getSize("toolbox_thumbnail_large").width
height: thumbnail.height + packageName.height
Rectangle
{
id: highlight
anchors.fill: parent
opacity: 0.0
color: UM.Theme.getColor("primary")
}
id: tileBase
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
height: thumbnail.height + packageNameBackground.height + (2 * UM.Theme.getSize("default_lining").width)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
color: "transparent"
Rectangle
{
id: thumbnail
color: "white"
width: UM.Theme.getSize("toolbox_thumbnail_large").width
height: UM.Theme.getSize("toolbox_thumbnail_large").height
border
{
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
anchors
{
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: UM.Theme.getSize("default_lining").width
}
Image
{
@ -43,22 +37,33 @@ Item
mipmap: true
}
}
Label
Rectangle
{
id: packageName
text: model.name
id: packageNameBackground
color: UM.Theme.getColor("primary")
anchors
{
top: thumbnail.bottom
horizontalCenter: parent.horizontalCenter
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
height: UM.Theme.getSize("toolbox_heading_label").height
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
Label
{
id: packageName
text: model.name
anchors
{
horizontalCenter: parent.horizontalCenter
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
height: UM.Theme.getSize("toolbox_heading_label").height
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("button_text")
font: UM.Theme.getFont("medium_bold")
}
}
MouseArea
{
@ -66,13 +71,15 @@ Item
hoverEnabled: true
onEntered:
{
thumbnail.border.color = UM.Theme.getColor("primary")
highlight.opacity = 0.1
packageName.color = UM.Theme.getColor("button_text_hover")
packageNameBackground.color = UM.Theme.getColor("primary_hover")
tileBase.border.color = UM.Theme.getColor("primary_hover")
}
onExited:
{
thumbnail.border.color = UM.Theme.getColor("lining")
highlight.opacity = 0.0
packageName.color = UM.Theme.getColor("button_text")
packageNameBackground.color = UM.Theme.getColor("primary")
tileBase.border.color = UM.Theme.getColor("lining")
}
onClicked:
{

View file

@ -15,6 +15,7 @@ Item
Label
{
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
color: UM.Theme.getColor("text")
height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
verticalAlignment: Text.AlignVCenter
anchors
@ -25,6 +26,7 @@ Item
right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width
}
}
Button
{

View file

@ -234,6 +234,11 @@ class Toolbox(QObject, Extension):
if not self._dialog:
self._dialog = self._createDialog("Toolbox.qml")
if not self._dialog:
Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.")
return
self._dialog.show()
# Apply enabled/disabled state to installed plugins
@ -241,7 +246,10 @@ class Toolbox(QObject, Extension):
def _createDialog(self, qml_name: str) -> Optional[QObject]:
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name)
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
return None
path = os.path.join(plugin_path, "resources", "qml", qml_name)
dialog = self._application.createQmlComponent(path, {"toolbox": self})
return dialog

View file

@ -103,8 +103,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else:
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
return
#Create a list from the supported file formats string.
machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
@ -125,6 +129,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else:
writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"]))
if not writer:
Logger.log("e", "Unexpected error when trying to get the FileWriter")
return
#This function pauses with the yield, waiting on instructions on which printer it needs to print with.
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
self._sending_job.send(None) #Start the generator.
@ -205,6 +213,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
yield #To prevent having to catch the StopIteration exception.
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
# This is the callback when the job finishes, where the message is created
assert(self._write_job_progress_message is not None)
self._write_job_progress_message.hide()
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
@ -249,7 +259,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self.activePrinterChanged.emit()
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
self._progress_message.hide()
if self._progress_message is not None:
self._progress_message.hide()
self._compressing_gcode = False
self._sending_gcode = False

View file

@ -170,7 +170,10 @@ class DiscoverUM3Action(MachineAction):
Logger.log("d", "Creating additional ui components for UM3.")
# Create networking dialog
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
if not plugin_path:
return
path = os.path.join(plugin_path, "UM3InfoComponents.qml")
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
if not self.__additional_components_view:
Logger.log("w", "Could not create ui components for UM3.")

View file

@ -23,7 +23,7 @@ if TYPE_CHECKING:
#
# This way it won't freeze up the interface while sending those materials.
class SendMaterialJob(Job):
def __init__(self, device: "ClusterUM3OutputDevice"):
def __init__(self, device: "ClusterUM3OutputDevice") -> None:
super().__init__()
self.device = device #type: ClusterUM3OutputDevice

View file

@ -6,6 +6,7 @@ import io
from UM.VersionUpgrade import VersionUpgrade
deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"}
## Upgrades configurations from the state they were in at version 3.4 to the
# state they should be in at version 4.0.
@ -68,6 +69,9 @@ class VersionUpgrade34to40(VersionUpgrade):
parser["metadata"]["setting_version"] = "5"
self._resetConcentric3DInfillPattern(parser)
if "values" in parser:
for deleted_setting in deleted_settings:
del parser["values"][deleted_setting]
result = io.StringIO()
parser.write(result)

View file

@ -0,0 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the resulting config files.
import pytest #To register tests with.
import VersionUpgrade34to40 #The module we're testing.
## Creates an instance of the upgrader to test with.
@pytest.fixture
def upgrader():
return VersionUpgrade34to40.VersionUpgrade34to40()
test_upgrade_version_nr_data = [
("Empty config file",
"""[general]
version = 5
[metadata]
setting_version = 4
"""
)
]
## Tests whether the version numbers are updated.
@pytest.mark.parametrize("test_name, file_data", test_upgrade_version_nr_data)
def test_upgradeVersionNr(test_name, file_data, upgrader):
#Perform the upgrade.
_, upgraded_instances = upgrader.upgradePreferences(file_data, "<string>")
upgraded_instance = upgraded_instances[0]
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_instance)
#Check the new version.
assert parser["general"]["version"] == "6"
assert parser["metadata"]["setting_version"] == "5"

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from math import pi, sin, cos, sqrt
from typing import Dict
import numpy
@ -42,7 +43,7 @@ class X3DReader(MeshReader):
def __init__(self) -> None:
super().__init__()
self._supported_extensions = [".x3d"]
self._namespaces = {}
self._namespaces = {} # type: Dict[str, str]
# Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None