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