From 4f76882654c50180db11195b18df82b11677b859 Mon Sep 17 00:00:00 2001 From: Rafael Hautekiet Date: Mon, 13 Aug 2018 00:21:18 +0200 Subject: [PATCH] Added tests, reimplemented interface, support for writing ctm files --- README.md | 17 ++-- openctm/__init__.py | 2 +- openctm/io.py | 90 ++++++++++++------ setup.py | 10 +- tests/__init__.py | 0 .../{vierkanten_test.ctm => squares.ctm} | Bin tests/test-data/vierkanten_test_exported.ctm | Bin 0 -> 1710 bytes tests/test_basic.py | 31 ++++++ 8 files changed, 105 insertions(+), 45 deletions(-) create mode 100644 tests/__init__.py rename tests/test-data/{vierkanten_test.ctm => squares.ctm} (100%) create mode 100644 tests/test-data/vierkanten_test_exported.ctm create mode 100644 tests/test_basic.py diff --git a/README.md b/README.md index 2baf315..425b984 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Python-OpenCTM ============== [![Build Status](https://travis-ci.org/lejafar/Python-OpenCTM.svg?branch=master)](https://travis-ci.org/lejafar/Python-OpenCTM) -### Python Interface for the Open-CTM Library +### Python Interface for the Open-CTM File Format Python-OpenCTM is an (unofficial) Python interface for the [OpenCTM](https://github.com/Danny02/OpenCTM) file format. A format that allows a geometry to be compressed to a fraction of comparable file formats (3DS, STL, COLLADA...). @@ -16,15 +16,12 @@ pip install python-openctm ```python import openctm -with openctm.open('foo.ctm') as ctm_file: - vertices = ctm_file.get_vertices() - faces = ctm_file.get_faces() +# read +mesh = openctm.import_mesh('foo.ctm') - result = {'vertices': vertices, - 'faces': faces} +print(mesh.vertices.shape) +# (124, 3) - # get face normals if available - face_normals = ctm_file.get_face_normals() - if face_normals: - result['face_normals'] = face_normals +# write +openctm.export_mesh(mesh, 'bar.ctm') ``` diff --git a/openctm/__init__.py b/openctm/__init__.py index 4178383..622ecfe 100644 --- a/openctm/__init__.py +++ b/openctm/__init__.py @@ -1 +1 @@ -from .io import open, CtmFile +from .io import import_mesh, export_mesh diff --git a/openctm/io.py b/openctm/io.py index c99d05c..7742c82 100644 --- a/openctm/io.py +++ b/openctm/io.py @@ -1,48 +1,80 @@ from .openctm import * import numpy as np -from contextlib import contextmanager -@contextmanager -def open(filename): - ctm_context = ctmNewContext(CTM_IMPORT) - yield CtmFile(ctm_context, filename) - ctmFreeContext(ctm_context) -class CtmFile: +class CTM: + + def __init__(self, _vertices, _faces, _normals=None): + self.vertices = _vertices + self.faces = _faces + self.normals = _normals + + def __eq__(self, other): + return (self.vertices == other.vertices).all() and (self.faces == other.faces).all() + - def __init__(self, ctm_context, filename): - self.ctm = ctm_context - ctmLoad(self.ctm, str(filename).encode('utf-8')) - err = ctmGetError(self.ctm) +def import_mesh(_filename): + ctm_context = ctmNewContext(CTM_IMPORT) + try: + ctmLoad(ctm_context, str(_filename).encode('utf-8')) + err = ctmGetError(ctm_context) if err != CTM_NONE: - raise IOError("Error loading file: " + str(ctmErrorString(err))) + raise IOError("Error loading file: %s" % str(ctmErrorString(err))) + + # read vertices + vertex_count = ctmGetInteger(ctm_context, CTM_VERTEX_COUNT) + vertex_ctm = ctmGetFloatArray(ctm_context, CTM_VERTICES) - def get_vertices(self): - # get vertices - vertex_count = ctmGetInteger(self.ctm, CTM_VERTEX_COUNT) - vertex_ctm = ctmGetFloatArray(self.ctm, CTM_VERTICES) - # use fromiter to avoid loop vertices = np.fromiter(vertex_ctm, dtype=np.float, count=vertex_count * 3).reshape((-1, 3)) - return vertices - def get_faces(self): - # get faces - face_count = ctmGetInteger(self.ctm, CTM_TRIANGLE_COUNT) - face_ctm = ctmGetIntegerArray(self.ctm, CTM_INDICES) + # read faces + face_count = ctmGetInteger(ctm_context, CTM_TRIANGLE_COUNT) + face_ctm = ctmGetIntegerArray(ctm_context, CTM_INDICES) faces = np.fromiter(face_ctm, dtype=np.int, count=face_count * 3).reshape((-1, 3)) - return faces - def get_face_normals(self): - if ctmGetInteger(self.ctm, CTM_HAS_NORMALS) == CTM_TRUE: - normals_ctm = ctmGetFloatArray(self.ctm, CTM_NORMALS) + # read face normals + normals = None + if ctmGetInteger(ctm_context, CTM_HAS_NORMALS) == CTM_TRUE: + normals_ctm = ctmGetFloatArray(ctm_context, CTM_NORMALS) normals = np.fromiter(normals_ctm, dtype=np.float, count=face_count * 3).reshape((-1, 3)) - return normals - # if not available return None + finally: + ctmFreeContext(ctm_context) + + return CTM(vertices, faces, normals) + + +def export_mesh(_ctm, _filename): + ctm_context = ctmNewContext(CTM_EXPORT) + + try: + vertex_count = len(_ctm.vertices) + vertices = _ctm.vertices.reshape((-1, 1)) + p_vertices = ctypes.cast((CTMfloat * vertex_count * 3)(), ctypes.POINTER(CTMfloat)) + for i in range(vertex_count * 3): + p_vertices[i] = CTMfloat(vertices[i]) + + face_count = len(_ctm.faces) + faces = _ctm.faces.reshape((-1, 1)) + p_faces = ctypes.cast((CTMuint * face_count * 3)(), ctypes.POINTER(CTMuint)) + for i in range(face_count * 3): + p_faces[i] = CTMuint(faces[i]) + + if _ctm.normals: + normal_count = len(_ctm.normals) + normals = _ctm.normals.reshape((-1, 1)) + p_normals = ctypes.cast((CTMfloat * normal_count * 3)(), ctypes.POINTER(CTMfloat)) + for i in range(normal_count * 3): + p_normals[i] = CTMfloat(normals[i]) else: - return None + p_normals = None + + ctmDefineMesh(ctm_context, p_vertices, CTMuint(vertex_count), p_faces, CTMuint(face_count), p_normals) + ctmSave(ctm_context, ctypes.c_char_p(_filename.encode('utf-8'))) + finally: + ctmFreeContext(ctm_context) diff --git a/setup.py b/setup.py index 32de520..bf29e69 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,10 @@ -from setuptools import setup, Distribution, Extension -import sys +from setuptools import setup, Extension setup( name='python-openctm', - version='1.0.2', - description='Python Interface for the OpenCTM Library', - long_description='Python Interface for the OpenCTM Library', + version='1.0.3', + description='Python Interface for the OpenCTM File Format', + long_description='Python Interface for the OpenCTM File Format', url='https://github.com/lejafar/python-openctm', author='Rafael Hautekiet', author_email='rafael.hautekiet@oqton.ai', @@ -24,6 +23,7 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Natural Language :: English", "Topic :: Scientific/Engineering", "License :: OSI Approved :: zlib/libpng License", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test-data/vierkanten_test.ctm b/tests/test-data/squares.ctm similarity index 100% rename from tests/test-data/vierkanten_test.ctm rename to tests/test-data/squares.ctm diff --git a/tests/test-data/vierkanten_test_exported.ctm b/tests/test-data/vierkanten_test_exported.ctm new file mode 100644 index 0000000000000000000000000000000000000000..892232d605e689a183698a228233d36f2c0fa122 GIT binary patch literal 1710 zcmV;f22uG>LsU%#0000@M==0=0000S0RR910000000000000000000>RYL}Z!{EV}hPLW)$XgtDh6Dc-Fa;afmpG$@spg zuk!)indLMk{7M28r6wHrq3zXriF5I8&oXN@mAL&HCW=^GRXN~)X6*G!q zXs$UD`CtK;>U5Z{yyZw@i-*93%5cj@({c={0t(MNXAt^12%DIOF+IrK6;b(d!=A|j zNZ(TMm9WE-J>eXvL0Ku`SC7gl$C0-L%}HufsDiT!+YI4a1rPR07lD451hf_2&3zjk z8r5Pj6NAewxyJelaiQO-yE8#@#CierEI10S+Ss}?%eG6K2O}9oc_z_3+04$O<8>{;HK&=0nF8Y;-m%P>4{^n_yAT#QdC9+003P800961 zV333iHK632>_9goI;&FyZix3YuSZpczwp|HgiNObH4XFt=d$C&j(j-~IZP23EaC-~ z+Q(V~6JM+fGjh(@9Zusi%>dud?ll;EuG^P&i$2{{#qVF1lnAHofe|}Zc zj)wkh()lMg=6Q)tEpQFvT~?%ozl};am8ZP*DmeA|)dvsowiKa$4U75x#`Os|W zmPz#bZNRUrHpHg6EO5!;%&g=v#{H4DjiYL#Qi5Cu-hTaoK!nrnr$*vUgD8>*X&E{Q zmSq(L>une!g^=^#!q(j!@WiS(`x}WpnLw7ub&yJpn?PO6BFTj4XSUjRKhj179w(D! zkm$2J3bJij?d%PNgPqi6J<=Q2V@VLkxhg3TnpkL&gVw`L*j~iR1xH{Ae%G&mV%XO{ z!l?5la3tE(I8i*&r%RB_qIp7XF7`w!*s*^YY#nF14D!+8YWn3+HW~7o5F{+iEtP0;E>wl*K9kv@B0U396rPb; z_t$FQX2HZB!x1YVta6EnUo>!gZO$q=;Dxc3Xi*7+d)c|dtRd%BA!JusXN4zmOw0ub zzijmJrwszew$a3KhTa^o6>puGEce6h`nQM(%f3}zOh?U5{^JJYZoIxc=YK84xnL6S z=2$0)UklaN?=ofr17{g)?aCCRNYUTc_Mwu0wRY21IN8i~o)9TOGorDhGY~@Q7~Ft; z1wdH@QPN?`ocTsDLkZx;%3N2E@1%^=G=F%xSZ0oZfTdcc0(=N=4>n$hJMzZ>y%tq$ zyp4LUvG?RgrS+mfqS!^vQ`(qdDrL;&*;v56CVYC1f7}>UXKCGn29M+e~nP7{QBm0w%;>4OCNS797=d5 E00uTYzyJUM literal 0 HcmV?d00001 diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..02cdf6d --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,31 @@ +import unittest +import os + +from openctm import import_mesh, export_mesh + + +class BasicTestSuiteFunctions(unittest.TestCase): + + def setUp(self): + self.file_dir = "tests/test-data/" + + def testImportVertices(self): + mesh = import_mesh("%s/squares.ctm" % self.file_dir) + assert len(mesh.vertices) == 124 + + def testImportFaces(self): + mesh = import_mesh("%s/squares.ctm" % self.file_dir) + assert len(mesh.faces) == 284 + + def testExportImport(self): + original_mesh = import_mesh("%s/squares.ctm" % self.file_dir) + export_mesh(original_mesh, "%s/squares_exported.ctm" % self.file_dir) + + exported_mesh = import_mesh("%s/squares_exported.ctm" % self.file_dir) + + assert original_mesh == exported_mesh + os.remove("%s/squares_exported.ctm" % self.file_dir) + + +if __name__ == '__main__': + unittest.main()