Added the stl/obj mediatype.
This commit is contained in:
parent
62cc81fb99
commit
76918e52e0
27
mediagoblin/media_types/stl/__init__.py
Normal file
27
mediagoblin/media_types/stl/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mediagoblin.media_types.stl.processing import process_stl, \
|
||||
sniff_handler
|
||||
|
||||
|
||||
MEDIA_MANAGER = {
|
||||
"human_readable": "stereo lithographics",
|
||||
"processor": process_stl,
|
||||
"sniff_handler": sniff_handler,
|
||||
"display_template": "mediagoblin/media_displays/stl.html",
|
||||
"default_thumb": "images/media_thumbs/video.jpg",
|
||||
"accepted_extensions": ["obj", "stl"]}
|
17
mediagoblin/media_types/stl/migrations.py
Normal file
17
mediagoblin/media_types/stl/migrations.py
Normal file
@ -0,0 +1,17 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
MIGRATIONS = {}
|
153
mediagoblin/media_types/stl/model_loader.py
Normal file
153
mediagoblin/media_types/stl/model_loader.py
Normal file
@ -0,0 +1,153 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
class ThreeDeeParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ThreeDee():
|
||||
"""
|
||||
3D model parser base class. Derrived classes are used for basic
|
||||
analysis of 3D models, and are not intended to be used for 3D
|
||||
rendering.
|
||||
"""
|
||||
|
||||
def __init__(self, fileob):
|
||||
self.verts = []
|
||||
self.average = [0, 0, 0]
|
||||
self.min = [None, None, None]
|
||||
self.max = [None, None, None]
|
||||
self.width = 0 # x axis
|
||||
self.depth = 0 # y axis
|
||||
self.height = 0 # z axis
|
||||
|
||||
self.load(fileob)
|
||||
if not len(self.verts):
|
||||
raise ThreeDeeParseError("Empyt model.")
|
||||
|
||||
for vector in self.verts:
|
||||
for i in range(3):
|
||||
num = vector[i]
|
||||
self.average[i] += num
|
||||
if not self.min[i]:
|
||||
self.min[i] = num
|
||||
self.max[i] = num
|
||||
else:
|
||||
if self.min[i] > num:
|
||||
self.min[i] = num
|
||||
if self.max[i] < num:
|
||||
self.max[i] = num
|
||||
|
||||
for i in range(3):
|
||||
self.average[i]/=len(self.verts)
|
||||
|
||||
self.width = abs(self.min[0] - self.max[0])
|
||||
self.depth = abs(self.min[1] - self.max[1])
|
||||
self.height = abs(self.min[2] - self.max[2])
|
||||
|
||||
|
||||
def load(self, fileob):
|
||||
"""Override this method in your subclass."""
|
||||
pass
|
||||
|
||||
|
||||
class ObjModel(ThreeDee):
|
||||
"""
|
||||
Parser for textureless wavefront obj files. File format
|
||||
reference: http://en.wikipedia.org/wiki/Wavefront_.obj_file
|
||||
"""
|
||||
|
||||
def __vector(self, line, expected=3):
|
||||
nums = map(float, line.strip().split(" ")[1:])
|
||||
return tuple(nums[:expected])
|
||||
|
||||
def load(self, fileob):
|
||||
for line in fileob:
|
||||
if line[0] == "v":
|
||||
self.verts.append(self.__vector(line))
|
||||
|
||||
|
||||
class BinaryStlModel(ThreeDee):
|
||||
"""
|
||||
Parser for ascii-encoded stl files. File format reference:
|
||||
http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
|
||||
"""
|
||||
|
||||
def __num(self, fileob, hint):
|
||||
assert hint == "uint" or hint == "real" or hint == "short"
|
||||
form = None
|
||||
bits = 0
|
||||
if hint == "uint":
|
||||
form = "<I" # little-endian unsigned int
|
||||
bits = 32
|
||||
elif hint == "real":
|
||||
form = "<i" # little-endian signed int
|
||||
bits = 32
|
||||
elif hint == "short":
|
||||
form = "<H" # little-endian unsigned short
|
||||
bits = 16
|
||||
return struct.unpack(form, fileob.read(bits/8))[0]
|
||||
|
||||
def __vector(self, fileob):
|
||||
return tuple([self.__num(fileob, "real") for n in range(3)])
|
||||
|
||||
def load(self, fileob):
|
||||
fileob.seek(80) # skip the header
|
||||
triangle_count = self.__num(fileob, "uint")
|
||||
for i in range(triangle_count):
|
||||
self.__vector(fileob) # skip the normal vector
|
||||
for v in range(3):
|
||||
# - FIXME - traingle_count IS reporting the correct
|
||||
# number, but the vertex information appears to be
|
||||
# total nonsense :(
|
||||
self.verts.append(self.__vector(fileob))
|
||||
self.__num(fileob, "short") # skip the attribute byte count
|
||||
|
||||
|
||||
def auto_detect(fileob, hint):
|
||||
"""
|
||||
Attempt to divine which parser to use to divine information about
|
||||
the model / verify the file."""
|
||||
|
||||
if hint == "obj" or not hint:
|
||||
try:
|
||||
return ObjModel(fileob)
|
||||
except ThreeDeeParseError:
|
||||
pass
|
||||
|
||||
if hint == "stl" or not hint:
|
||||
try:
|
||||
# HACK Ascii formatted stls are similar enough to obj
|
||||
# files that we can just use the same parser for both.
|
||||
# Isn't that something?
|
||||
return ObjModel(fileob)
|
||||
except ThreeDeeParseError:
|
||||
pass
|
||||
try:
|
||||
# It is pretty important that the binary stl model loader
|
||||
# is tried second, because its possible for it to parse
|
||||
# total garbage from plaintext =)
|
||||
return BinaryStlModel(fileob)
|
||||
except ThreeDeeParseError:
|
||||
pass
|
||||
except MemoryError:
|
||||
pass
|
||||
|
||||
raise ThreeDeeParseError("Could not successfully parse the model :(")
|
44
mediagoblin/media_types/stl/models.py
Normal file
44
mediagoblin/media_types/stl/models.py
Normal file
@ -0,0 +1,44 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from mediagoblin.db.sql.base import Base
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, Integer, Float, ForeignKey)
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
|
||||
|
||||
class StlData(Base):
|
||||
__tablename__ = "stl__mediadata"
|
||||
|
||||
# The primary key *and* reference to the main media_entry
|
||||
media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
|
||||
primary_key=True)
|
||||
get_media_entry = relationship("MediaEntry",
|
||||
backref=backref("stl__media_data", cascade="all, delete-orphan"))
|
||||
|
||||
center_x = Column(Float)
|
||||
center_y = Column(Float)
|
||||
center_z = Column(Float)
|
||||
|
||||
width = Column(Float)
|
||||
height = Column(Float)
|
||||
depth = Column(Float)
|
||||
|
||||
|
||||
DATA_MODEL = StlData
|
||||
MODELS = [StlData]
|
107
mediagoblin/media_types/stl/processing.py
Normal file
107
mediagoblin/media_types/stl/processing.py
Normal file
@ -0,0 +1,107 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from mediagoblin import mg_globals as mgg
|
||||
from mediagoblin.processing import create_pub_filepath, \
|
||||
FilenameBuilder
|
||||
|
||||
from mediagoblin.media_types.stl import model_loader
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
SUPPORTED_FILETYPES = ['stl', 'obj']
|
||||
|
||||
|
||||
def sniff_handler(media_file, **kw):
|
||||
if kw.get('media') is not None:
|
||||
name, ext = os.path.splitext(kw['media'].filename)
|
||||
clean_ext = ext[1:].lower()
|
||||
|
||||
if clean_ext in SUPPORTED_FILETYPES:
|
||||
_log.info('Found file extension in supported filetypes')
|
||||
return True
|
||||
else:
|
||||
_log.debug('Media present, extension not found in {0}'.format(
|
||||
SUPPORTED_FILETYPES))
|
||||
else:
|
||||
_log.warning('Need additional information (keyword argument \'media\')'
|
||||
' to be able to handle sniffing')
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def process_stl(entry):
|
||||
"""
|
||||
Code to process an stl or obj model.
|
||||
"""
|
||||
|
||||
workbench = mgg.workbench_manager.create_workbench()
|
||||
# Conversions subdirectory to avoid collisions
|
||||
conversions_subdir = os.path.join(
|
||||
workbench.dir, 'conversions')
|
||||
os.mkdir(conversions_subdir)
|
||||
queued_filepath = entry.queued_media_file
|
||||
queued_filename = workbench.localized_file(
|
||||
mgg.queue_store, queued_filepath, 'source')
|
||||
name_builder = FilenameBuilder(queued_filename)
|
||||
|
||||
ext = queued_filename.lower().strip()[-4:]
|
||||
if ext.startswith("."):
|
||||
ext = ext[1:]
|
||||
else:
|
||||
ext = None
|
||||
|
||||
# Attempt to parse the model file and divine some useful
|
||||
# information about it.
|
||||
with open(queued_filename, 'rb') as model_file:
|
||||
model = model_loader.auto_detect(model_file, ext)
|
||||
|
||||
# TODO: generate blender previews
|
||||
|
||||
# Save the public file stuffs
|
||||
model_filepath = create_pub_filepath(
|
||||
entry, name_builder.fill('{basename}{ext}'))
|
||||
|
||||
with mgg.public_store.get_file(model_filepath, 'wb') as model_file:
|
||||
with open(queued_filename, 'rb') as queued_file:
|
||||
model_file.write(queued_file.read())
|
||||
|
||||
|
||||
# Remove queued media file from storage and database
|
||||
mgg.queue_store.delete_file(queued_filepath)
|
||||
entry.queued_media_file = []
|
||||
|
||||
# Insert media file information into database
|
||||
media_files_dict = entry.setdefault('media_files', {})
|
||||
media_files_dict[u'original'] = model_filepath
|
||||
media_files_dict[u'thumb'] = ["mgoblin_static/images/404.png"]
|
||||
|
||||
# Put model dimensions into the database
|
||||
dimensions = {
|
||||
"center_x" : model.average[0],
|
||||
"center_y" : model.average[1],
|
||||
"center_z" : model.average[2],
|
||||
"width" : model.width,
|
||||
"height" : model.height,
|
||||
"depth" : model.depth,
|
||||
}
|
||||
entry.media_data_init(**dimensions)
|
||||
|
||||
# clean up workbench
|
||||
workbench.destroy_self()
|
Loading…
x
Reference in New Issue
Block a user