Commit 1630be10 authored by sumpfralle's avatar sumpfralle

added drag'n'drop feature for PyCAM's window (loading model files)

enable non-local file handling (e.g. http, ...)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@976 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent dfbffd2b
......@@ -13,6 +13,8 @@ Version 0.4.1 - UNRELEASED
* added a configuration setting for automatically loading a custom task settings file on startup
* added a simple "undo" feature for reversing model manipulations
* added a minimum step width for GCode positions to be written
* enabled drag'n'drop for loading models
* support non-local file handling (e.g. http, ...)
* the scroll wheel now behaves similarly to Inkscape's interface (pan and zoom)
* the default filename extension for exported GCode files is now configurable
* unify DropCutter behaviour for models that are higher than the defined bounding box
......
......@@ -6262,7 +6262,7 @@ Hotkey: &lt;p&gt;</property>
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;span foreground="red"&gt;Warning: no local processes enabled.
You will need a remote server.&lt;/span&gt;</property>
You will need remote workers.&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
......@@ -6361,7 +6361,7 @@ PyCAM's default port is 1250.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Type in a hostname or IP address if you want to connect to a remote PyCAM server.
Leave this field empty for a local-only server.</property>
Leave this field empty for running a local server.</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">18</property>
<property name="truncate_multiline">True</property>
......@@ -6550,7 +6550,10 @@ Please check the following list of possible reasons and fix the problem, if poss
1) You are using the Windows standalone executable.
Sadly this problem is not too easy to solve. Future releases of PyCAM should remove this limitation.
2) You are using Python 2.5 or below and the package "multiprocessing" is &lt;i&gt;not&lt;/i&gt; installed.</property>
2) You are using Python 2.5 or below and the package "multiprocessing" is &lt;i&gt;not&lt;/i&gt; installed.
See the &lt;a href="http://sf.net/apps/mediawiki/pycam/?title=Parallel_Processing_on_different_Platforms"&gt;platform feature matrix&lt;/a&gt; for details.
</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
......
......@@ -39,7 +39,7 @@ from pycam.Gui.OpenGLTools import ModelViewWindowGL
from pycam.Geometry.Letters import TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, \
TEXT_ALIGN_RIGHT
import pycam.Geometry.Model
from pycam.Utils import ProgressCounter
from pycam.Utils import ProgressCounter, check_uri_exists
from pycam.Toolpath import Bounds
from pycam import VERSION
import pycam.Physics.ode_physics
......@@ -362,6 +362,8 @@ class ProjectGui:
gtk.accel_map_change_entry(accel_path, key, mod, True)
# no undo is allowed at the beginning
self.gui.get_object("UndoButton").set_sensitive(False)
# configure drag-n-drop for config files and models
self.configure_drag_and_drop(self.window)
# other events
self.window.connect("destroy", self.destroy)
# the settings window
......@@ -2131,6 +2133,8 @@ class ProjectGui:
for name in ("GeneralSettings", "Help3DView")])
if self.model and self.view3d.enabled:
self.view3d.reset_view()
# configure drag-and-drop for the 3D window
self.configure_drag_and_drop(self.view3d.window)
# disable the "toggle" button, if the 3D view does not work
toggle_3d_checkbox.set_sensitive(self.view3d.enabled)
else:
......@@ -2748,6 +2752,39 @@ class ProjectGui:
def quit(self):
self.save_preferences()
def configure_drag_and_drop(self, obj):
obj.connect("drag-data-received", self.handle_data_drop)
flags = gtk.DEST_DEFAULT_ALL
targets = [("text/uri-list", gtk.TARGET_OTHER_APP, 0),
("text/plain", gtk.TARGET_OTHER_APP, 1)]
actions = gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | \
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE | \
gtk.gdk.ACTION_ASK
obj.drag_dest_set(flags, targets, actions)
def handle_data_drop(self, widget, drag_context, x, y, selection_data, info,
timestamp):
if info != 0:
uris = [str(selection_data.data)]
else:
uris = selection_data.get_uris()
if not uris:
# empty selection
return True
for uri in uris:
file_type, importer = pycam.Importers.detect_file_type(uri,
quiet=True)
if importer:
# looks like the file can be loaded
if self.load_model_file(filename=uri):
return True
if len(uris) > 1:
log.error("Failed to open any of the given models: %s" % \
str(uris))
else:
log.error("Failed to open the model: %s" % str(uris[0]))
return False
def append_to_queue(self, func, *args, **kwargs):
# check if gui is currently active
if self.gui_is_active:
......@@ -2759,16 +2796,7 @@ class ProjectGui:
def load_recent_model_file(self, widget):
uri = widget.get_current_uri()
if uri.startswith("file://"):
parsed = urllib.unquote(uri[len("file://"):])
self.load_model_file(filename=parsed)
else:
message = "Sorry - PyCAM can currently load only local files."
window = gtk.MessageDialog(self.window, type=gtk.MESSAGE_WARNING,
buttons=gtk.BUTTONS_OK, message_format=message)
window.set_title("Unsupported file location specified")
window.run()
window.destroy()
self.load_model_file(filename=uri)
@gui_activity_guard
@progress_activity_guard
......@@ -2796,8 +2824,12 @@ class ProjectGui:
callback=self.update_progress_bar)):
self.set_model_filename(filename)
self.add_to_recent_file_list(filename)
return True
else:
return False
else:
log.error("Failed to detect filetype!")
return False
@gui_activity_guard
def export_emc_tools(self, widget=None, filename=None):
......@@ -3744,7 +3776,7 @@ class ProjectGui:
response = overwrite_window.run()
overwrite_window.destroy()
done = (response == gtk.RESPONSE_YES)
elif mode_load and not os.path.isfile(filename):
elif mode_load and not check_uri_exists(filename):
not_found_window = gtk.MessageDialog(self.window, type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_OK,
message_format="This file does not exist. Please choose a different filename.")
......@@ -3763,16 +3795,21 @@ class ProjectGui:
def add_to_recent_file_list(self, filename):
# Add the item to the recent files list - if it already exists.
# Otherwise it will be added later after writing the file.
if os.path.isfile(filename):
if check_uri_exists(filename):
# skip this, if the recent manager is not available (e.g. GTK 2.12.1 on Windows)
if self.recent_manager:
# Convert the local path to a URI filename style. This is
# specifically necessary under Windows (due to backslashs).
filename_url_local = urllib.pathname2url(
os.path.abspath(filename))
# join the "file:" scheme with the url
filename_url = urlparse.urlunparse(("file", None,
filename_url_local, None, None, None))
if os.path.exists(filename):
# This is a local file.
# Convert the local path to a URI filename style. This is
# specifically necessary under Windows (due to backslashs).
filename_url_local = urllib.pathname2url(
os.path.abspath(filename))
# join the "file:" scheme with the url
filename_url = urlparse.urlunparse(("file", None,
filename_url_local, None, None, None))
else:
# this is a remote file - or it already contains "file://"
filename_url = filename
self.recent_manager.add_item(filename_url)
# store the directory of the last loaded file
self.last_dirname = os.path.dirname(os.path.abspath(filename))
......
......@@ -25,6 +25,7 @@ from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point
from pycam.Geometry import get_points_of_arc
import pycam.Utils.log
from pycam.Utils import open_url
log = pycam.Utils.log.get_logger()
......@@ -173,7 +174,7 @@ class CXFParser(object):
def import_font(filename, callback=None):
try:
infile = open(filename,"r")
infile = open_url(filename)
except IOError, err_msg:
log.error("CXFImporter: Failed to read file (%s): %s" \
% (filename, err_msg))
......
......@@ -25,6 +25,7 @@ from pycam.Geometry.Line import Line
import pycam.Geometry.Model
import pycam.Geometry
import pycam.Utils.log
from pycam.Utils import open_url
log = pycam.Utils.log.get_logger()
......@@ -351,7 +352,7 @@ class DXFParser(object):
def import_model(filename, program_locations=None, unit=None,
color_as_height=False, callback=None):
try:
infile = open(filename,"rb")
infile = open_url(filename)
except IOError, err_msg:
log.error("DXFImporter: Failed to read file (%s): %s" \
% (filename, err_msg))
......
......@@ -22,6 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Importers.SVGImporter import convert_eps2dxf
import pycam.Importers.DXFImporter
from pycam.Utils import check_uri_exists, retrieve_uri
import tempfile
import os
......@@ -29,9 +30,23 @@ log = pycam.Utils.log.get_logger()
def import_model(filename, program_locations=None, unit="mm", callback=None):
if not os.path.isfile(filename):
if not check_uri_exists(filename):
log.error("PSImporter: file (%s) does not exist" % filename)
return None
if not os.path.isfile(filename):
# non-local file - write it to a temporary file first
local_file = False
uri = filename
ps_file_handle, ps_file_name = tempfile.mkstemp(suffix=".ps")
os.close(ps_file_handle)
log.debug("Retrieving PS file for local access: %s -> %s" % \
(uri, ps_file_name))
if not retrieve_uri(uri, ps_file_name, callback=callback):
log.error("PSImporter: Failed to retrieve the PS model file: " + \
"%s -> %s" % (uri, ps_file_name))
filename = ps_file_name
else:
local_file = True
if program_locations and "pstoedit" in program_locations:
pstoedit_path = program_locations["pstoedit"]
......@@ -51,6 +66,8 @@ def import_model(filename, program_locations=None, unit="mm", callback=None):
os.close(dxf_file_handle)
success = convert_eps2dxf(filename, dxf_file_name, unit=unit,
location=pstoedit_path)
if not local_file:
remove_temp_file(ps_file_name)
if not success:
result = None
elif callback and callback():
......
......@@ -27,10 +27,11 @@ from pycam.Geometry.PointKdtree import PointKdtree
from pycam.Geometry.utils import epsilon
from pycam.Geometry.Model import Model
import pycam.Utils.log
from pycam.Utils import open_url
from struct import unpack
import StringIO
import re
import os
log = pycam.Utils.log.get_logger()
......@@ -61,7 +62,11 @@ def ImportModel(filename, use_kdtree=True, program_locations=None, unit=None,
normal_conflict_warning_seen = False
try:
f = open(filename, "rb")
url_file = open_url(filename)
# urllib.urlopen objects do not support "seek" - so we need to read the
# whole file at once. This is ugly - anyone with a better idea?
f = StringIO.StringIO(url_file.read())
url_file.close()
except IOError, err_msg:
log.error("STLImporter: Failed to read file (%s): %s" \
% (filename, err_msg))
......@@ -85,9 +90,9 @@ def ImportModel(filename, use_kdtree=True, program_locations=None, unit=None,
numfacets = unpack("<I", f.read(4))[0]
binary = False
if os.path.getsize(filename) == (84 + 50*numfacets):
if f.len == (84 + 50*numfacets):
binary = True
elif header.find("solid")>=0 and header.find("facet")>=0:
elif header.find("solid") >= 0 and header.find("facet") >= 0:
binary = False
f.seek(0)
else:
......
......@@ -21,6 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Importers.DXFImporter
from pycam.Utils import check_uri_exists, retrieve_uri
import tempfile
import subprocess
import os
......@@ -81,9 +82,23 @@ def convert_eps2dxf(eps_filename, dxf_filename, location=None, unit="mm"):
return False
def import_model(filename, program_locations=None, unit="mm", callback=None):
if not os.path.isfile(filename):
if not check_uri_exists(filename):
log.error("SVGImporter: file (%s) does not exist" % filename)
return None
if not os.path.isfile(filename):
# non-local file - write it to a temporary file first
local_file = False
uri = filename
svg_file_handle, svg_file_name = tempfile.mkstemp(suffix=".svg")
os.close(svg_file_handle)
log.debug("Retrieving SVG file for local access: %s -> %s" % \
(uri, svg_file_name))
if not retrieve_uri(uri, svg_file_name, callback=callback):
log.error("SVGImporter: Failed to retrieve the SVG model file: " + \
"%s -> %s" % (uri, svg_file_name))
filename = svg_file_name
else:
local_file = True
if program_locations and "inkscape" in program_locations:
inkscape_path = program_locations["inkscape"]
......@@ -112,6 +127,8 @@ def import_model(filename, program_locations=None, unit="mm", callback=None):
log.warn("SVGImporter: failed to remove temporary file " \
+ "(%s): %s" % (filename, err_msg))
# remove the temporary file
if not local_file:
remove_temp_file(svg_file_name)
if not success:
remove_temp_file(eps_file_name)
return None
......
......@@ -23,6 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Gui.Settings
import pycam.Gui.Project
import pycam.Utils.log
from pycam.Utils import open_url
import re
import os
import sys
......@@ -58,7 +59,7 @@ def parse_toolpath_settings(filename):
else:
# open the file
try:
infile = open(filename,"r")
infile = open_url(filename)
except IOError, err_msg:
log.warn("ToolpathSettingsParser: Failed to read file (%s): %s" % \
(filename, err_msg))
......
......@@ -34,25 +34,23 @@ import os
log = pycam.Utils.log.get_logger()
def detect_file_type(filename):
def detect_file_type(filename, quiet=False):
failure = (None, None)
if not os.path.isfile(filename):
return failure
# check all listed importers
# TODO: this should be done by evaluating the header of the file
if filename.lower().endswith(".stl"):
return ("stl", pycam.Importers.STLImporter.ImportModel)
elif filename.lower().endswith(".dxf"):
return ("dxf", pycam.Importers.DXFImporter.import_model)
elif filename.lower().endswith(".svg"):
return ("svg", pycam.Importers.SVGImporter.import_model)
elif filename.lower().endswith(".eps") \
or filename.lower().endswith(".ps"):
return ("ps", pycam.Importers.PSImporter.import_model)
else:
# check all listed importers
# TODO: this should be done by evaluating the header of the file
if filename.lower().endswith(".stl"):
return ("stl", pycam.Importers.STLImporter.ImportModel)
elif filename.lower().endswith(".dxf"):
return ("dxf", pycam.Importers.DXFImporter.import_model)
elif filename.lower().endswith(".svg"):
return ("svg", pycam.Importers.SVGImporter.import_model)
elif filename.lower().endswith(".eps") \
or filename.lower().endswith(".ps"):
return ("ps", pycam.Importers.PSImporter.import_model)
else:
if not quiet:
log.error(("Importers: Failed to detect the model type of '%s'. " \
+ "Is the file extension (stl/dxf/svg/eps/ps) correct?") \
% filename)
return failure
return failure
......@@ -27,6 +27,7 @@ __all__ = ["iterators", "polynomials", "ProgressCounter", "threading",
import sys
import os
import socket
import urllib
# this is imported below on demand
#import win32com
#import win32api
......@@ -55,6 +56,29 @@ def get_platform():
return PLATFORM_UNKNOWN
def open_url(uri):
return urllib.urlopen(uri)
def check_uri_exists(uri):
try:
handle = open_url(uri)
handle.close()
return True
except IOError:
return False
def retrieve_uri(uri, filename, callback=None):
if callback:
download_callback = lambda current_blocks, block_size, num_of_blocks: \
callback()
else:
download_callback = None
try:
urllib.urlretrieve(uri, filename, download_callback)
return True
except IOError:
return False
def get_all_ips():
""" try to get all IPs of this machine
......@@ -109,7 +133,7 @@ def get_external_program_location(key):
for one_dir in path_env.split(os.pathsep):
for basename in potential_names:
location = os.path.join(one_dir, basename)
if os.path.isfile(location):
if check_uri_exists(location):
return location
# do a manual scan in the programs directory (only for windows)
try:
......@@ -123,7 +147,7 @@ def get_external_program_location(key):
for sub_dir in windows_program_directories[key]:
for basename in potential_names:
location = os.path.join(program_dir, sub_dir, basename)
if os.path.isfile(location):
if check_uri_exists(location):
return location
# nothing found
return None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment