Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance optimization of STL loading #1251

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fixed typo in name `Rhino.Geometry.MeshingParameters` in `compas_rhino.geometry.RhinoBrep.to_meshes()`.
* Fixed `TypeErrorException` when serializing a `Mesh` which has been converted from Rhino.
* Fixed color conversions in `compas_rhion.conversions.mesh_to_compas`.
* Fixed double downloading of STL files caused by binary/ascii detection.
* Improved download speed of STL files when running in IronPython by switching to a .NET-based downloadd.

### Removed

Expand Down
42 changes: 36 additions & 6 deletions src/compas/_iotools.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
"""For the time being, these functions are only for internal use."""
from contextlib import contextmanager
from __future__ import print_function

import io
import platform
from contextlib import contextmanager

if "ironpython" == platform.python_implementation().lower():
from System import Array
from System import Byte
from System.IO import MemoryStream
from System.Net import SecurityProtocolType
from System.Net import ServicePointManager
from System.Net import WebRequest

def download_url_as_bytes(url):
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12

request = WebRequest.Create(url)

try:
response = request.GetResponse()
responseStream = response.GetResponseStream()

memoryStream = MemoryStream()
buffer = Array.CreateInstance(Byte, 8192)
bytesRead = responseStream.Read(buffer, 0, buffer.Length)

while bytesRead > 0:
memoryStream.Write(buffer, 0, bytesRead)
bytesRead = responseStream.Read(buffer, 0, buffer.Length)

return memoryStream.ToArray()

else:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen

def download_url_as_bytes(url):
response = urlopen(url)
return response.read()


@contextmanager
Expand Down Expand Up @@ -45,8 +76,7 @@ def open_file(file_or_filename, mode="r"):
# support all read modes (r, r+, rb, etc)
if not mode.startswith("r"):
raise ValueError("URLs can only be opened in read mode.")
response = urlopen(file)
file = io.BytesIO(response.read())
file = io.BytesIO(download_url_as_bytes(file))
else:
file = open(file, mode=mode)
close_source = True
Expand Down
42 changes: 20 additions & 22 deletions src/compas/files/stl.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,26 @@ def read(self):

"""
is_binary = False
with _iotools.open_file(self.filepath, "rb") as file:
line = file.readline().strip()

with _iotools.open_file(self.filepath, "rb") as self.file:
line = self.file.readline().strip()
if b"solid" in line:
is_binary = False
else:
is_binary = True
try:
if not is_binary:
self._read_ascii()
else:
try:
if not is_binary:
with _iotools.open_file(self.filepath, "r") as self.file:
self._read_ascii()
else:
self._read_binary()
except Exception:
# raise if it was already detected as binary, but failed anyway
if is_binary:
raise
# else, ascii parsing failed, try binary
is_binary = True
self._read_binary()
except Exception:
# raise if it was already detected as binary, but failed anyway
if is_binary:
raise
# else, ascii parsing failed, try binary
is_binary = True
self._read_binary()

# ==========================================================================
# ascii
Expand All @@ -151,10 +153,8 @@ def read(self):
# ==========================================================================

def _read_ascii(self):
with _iotools.open_file(self.filepath, "r") as file:
self.file = file
self.file.seek(0)
self.facets = self._read_solids_ascii()
self.file.seek(0)
self.facets = self._read_solids_ascii()

def _read_solids_ascii(self):
if not self.file:
Expand Down Expand Up @@ -233,11 +233,9 @@ def _read_uint32(self):
return struct.unpack("<I", bytes_)[0]

def _read_binary(self):
with _iotools.open_file(self.filepath, "rb") as file:
self.file = file
self.file.seek(0)
self.header = self._read_header_binary()
self.facets = self._read_facets_binary()
self.file.seek(0)
self.header = self._read_header_binary()
self.facets = self._read_facets_binary()

def _read_header_binary(self):
bytes_ = self.file.read(80)
Expand Down
Loading