Added the stl/obj mediatype.

This commit is contained in:
Aeva Ntsc 2012-10-15 01:36:26 -05:00 committed by Christopher Allan Webber
parent 62cc81fb99
commit 76918e52e0
5 changed files with 348 additions and 0 deletions

View 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"]}

View 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 = {}

View 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 :(")

View 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]

View 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()