diff --git a/README.rst b/README.rst index d13f1b1..86fa509 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,26 @@ there, run setup.py like so:: Then install:: python setup.py install + +Windows Instructions +-------------------- + +It seems that cspice neesds to be rebuilt to work with common Windows Python +code bases. To accomplish this:: + +1. Download the cygwin gCC 32bit Windows Spice toolkit here + http://naif.jpl.nasa.gov/naif/toolkit_C.html +2. Extract the file into the PySpice folder. This will create + a cspice folder right next to spice and tests. +3. Delete cspice\lib\* We need to re-build these for your platform. +3. Run mkproduct_cspice.bat with a build environment that supports + your python distribution. If you don't do much development, but + have Enthoguht Python (EPD or Canopy), this appears to just work. +4. Run `python setup.py build_ext -c mingw32` + THE `-c mingw32` is VERY important! +5. + + 64 bit vs 32 bit ---------------- diff --git a/mkprodct_cspice.bat b/mkprodct_cspice.bat new file mode 100644 index 0000000..8d972d6 --- /dev/null +++ b/mkprodct_cspice.bat @@ -0,0 +1,76 @@ +rem +rem mkcspice.bat Adapted to build cspice under windows / msys +rem +rem Creates cspice.lib for MS Visual C++ and moves it to the +rem appropriate Toolkit directory. +rem +rem +rem Version 3.1.0 19-OCT-2003 (BVS) +rem +rem added -DNON_ANSI_STDIO compile option. +rem +rem Version 3.0.0 03-NOV-1999 (NJB) +rem +rem fixed the last "set cl" command. +rem +rem Version 2.0.0 26-FEB-1999 (NJB) +rem +rem Added OMIT_BLANK_CC preprocessor flag. +rem +rem Version 1.0.0 29-DEC-1998 (NJB) +rem + +cd cspice\src\cspice + +set cl= /c /O2 -D_COMPLEX_DEFINED -DMSDOS -DOMIT_BLANK_CC -DNON_ANSI_STDIO + +rem +rem The optimization algorithm has a very tough time with zzsecptr.c, +rem so exempt this routine from optimization. +rem + +rename zzsecprt.c zzsecprt.x + +rem +rem Compile everything else. +rem + +REM BEGIN FISCHER MOD +REM for %%f in (*.c) do cl %%f +gcc -I..\..\include -c -O2 -D_COMPLEX_DEFINED -DOMIT_BLANK_CC -DNON_ANSI_STDIO -DMSDOS *.c +REM ENDFISCHERMOD + +rem +rem Set the cl variable to omit optimization. Compile zzsecprt.c. +rem + + +REM BEGIN FISCHER MOD +REM set cl= /c -D_COMPLEX_DEFINED -DMSDOS -DOMIT_BLANK_CC +REM END + +rename zzsecprt.x zzsecprt.c + +REM BEGIN FISCHER MOD +REM cl zzsecprt.c +gcc -I..\..\include -c -D_COMPLEX_DEFINED -DOMIT_BLANK_CC -DMSDOS zzsecprt.c +REM END + +dir /b *.o > temp.lst + +REM BEGIN FISCHER MOD +REM link -lib /out:cspice.lib @temp.lst + +REM gcc -o cspice.lib @temp.lst +ar -cq cspice.lib @temp.lst +gcc -shared -o cspice.dll @temp.lst +REM END FISCHER MOD + + +move cspice.lib ..\..\lib +move cspice.dll ..\..\lib + + +rem del *.o +del temp.lst + diff --git a/mkwrapper.py b/mkwrapper.py index 2f38905..6fed28d 100644 --- a/mkwrapper.py +++ b/mkwrapper.py @@ -84,6 +84,9 @@ def __setattr__(self, key, val): 'gfevnt_c', 'gffove_c', 'gfocce_c', 'gfuds_c', 'uddc_c', 'uddf_c', ) +custom_list = ('spkw10_c', 'getelm_c', + ) + module_defs = [] cspice_src = None @@ -200,8 +203,11 @@ def get_doc(function_name): return '"%s"' % doc.getvalue() -def gen_wrapper(prototype, buffer): - prototype = remove_extra_spaces(prototype) +def gen_function(prototype_obj, prototype_comment, python_function_name, buffer): + # declare the function for this wrapper + # I moved this block of code to a separate function for readability. It's + # still pretty cumbersome, but hey! it works. + manually_build_returnVal = False input_list = [] input_name_list = [] @@ -216,37 +222,12 @@ def gen_wrapper(prototype, buffer): # likely STRING_LEN above). string_output_num = 0 - # parse up the given prototype into its fundamental pieces. this function - # returns a container object with all the information parsed up. - prototype_obj = parse_prototype(prototype) - - # check the exclude list before continuing - if prototype_obj.function_name in exclude_list: return False # the string that is passed to PyArg_ParseTuple for getting the # arguments list and Py_BuildValue for returning results parse_tuple_string = "" buildvalue_string = "" - # remove the _c suffix for the python function name - python_function_name = prototype_obj.function_name.rsplit('_c',1)[0] - - # Add the C function prototype to the source code output. - t = '/* %s */' % prototype - prototype_comment_list = [] - while len(t) > 80: - count = 79 - while t[count-1] != ',' and count > 1: - count -= 1 - - prototype_comment_list.append(t[:count]) - t = t[count:] - if t: - prototype_comment_list.append(t) - - prototype_comment = '\n'.join(prototype_comment_list) - - # declare the function for this wrapper buffer.write( "\n%s\nstatic PyObject * spice_%s(PyObject *self, PyObject *args)\n{" % \ (prototype_comment, python_function_name)); @@ -534,7 +515,49 @@ def gen_wrapper(prototype, buffer): pass # for now; TODO: figure out what to do buffer.write("\n}"); + return python_function_name + + +def gen_wrapper(prototype, buffer): + prototype = remove_extra_spaces(prototype) + + + # parse up the given prototype into its fundamental pieces. this function + # returns a container object with all the information parsed up. + prototype_obj = parse_prototype(prototype) + + # remove the _c suffix for the python function name + python_function_name = prototype_obj.function_name.rsplit('_c',1)[0] + + + # check the exclude list before continuing. + if (prototype_obj.function_name in exclude_list and + prototype_obj.function_name not in custom_list ): return False + + # Add the C function prototype to the source code output. + t = '/* %s */' % prototype + prototype_comment_list = [] + while len(t) > 80: + count = 79 + while t[count-1] != ',' and count > 1: + count -= 1 + + prototype_comment_list.append(t[:count]) + t = t[count:] + if t: + prototype_comment_list.append(t) + + prototype_comment = '\n'.join(prototype_comment_list) + + # If it's not a custom command, add the function to the spicemodule.c output + if prototype_obj.function_name not in custom_list: + gen_function(prototype_obj, prototype_comment, python_function_name, buffer, ) + + # Custom commands still get a doc string and module_defs. I tried making + # them separate modules, but the static linking of cspice meant that modules + # furnsh'd under one module were not available under the custom module. + # dig out the function name from the source file doc = get_doc(prototype_obj.function_name) diff --git a/pyspice.h b/pyspice.h index 5c2dbcd..4f2f3ec 100644 --- a/pyspice.h +++ b/pyspice.h @@ -12,6 +12,8 @@ #include +#include "spicemodule_custom.h" + #ifndef __PYSPICE_H__ #define __PYSPICE_H__ 1 diff --git a/setup.py b/setup.py index 3649d97..29eaf34 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,9 @@ from shutil import copy from subprocess import PIPE, Popen +WINDOWS = os.sys.platform == 'win32' + + APP_NAME = os.path.basename(os.getcwd()) SHARE_DIR = os.path.join('share', 'spicedoc') @@ -28,14 +31,20 @@ class LibError(Exception): pass def build_cspice(): - libfile_path = os.path.join(CSPICE_SRC, 'lib', 'cspice.a') - if os.path.exists(libfile_path): + libfile_paths = (os.path.join(CSPICE_SRC, 'lib', 'cspice.a'), + os.path.join(CSPICE_SRC, 'lib', 'cspice.lib'), + ) + if any(os.path.exists(x) for x in libfile_paths): return curr_dir = os.getcwd() try: - os.chdir(CSPICE_SRC) - makeall = Popen('/bin/csh makeall.csh', shell=True) + if WINDOWS: + makeall = Popen('mkprodct_cspice.bat', shell=True) + + else: + os.chdir(CSPICE_SRC) + makeall = Popen('/bin/csh makeall.csh', shell=True) status = os.waitpid(makeall.pid, 0)[1] if status != 0: @@ -44,7 +53,11 @@ def build_cspice(): os.chdir(curr_dir) def find_libs(): - for libfile in ('cspice.a', 'csupport.a'): + if WINDOWS: + libfile_list = ('cspice.lib', ) + else: + libfile_list = ('cspice.a', ) + for libfile in libfile_list: libfile_path = os.path.join(CSPICE_SRC, 'lib', libfile) lib_libfile_path = os.path.join(CSPICE_SRC, 'lib', 'lib' + libfile) @@ -79,6 +92,19 @@ def set_build_paths(): path = os.path.join(CSPICE_SRC, dirname) sys.argv.append('%s%s' % (flag, path)) +spice_custom = Extension( + '_spice', + sources = ['pyspice.c', 'spicemodule.c', + 'spicemodule_custom.c' + ], + libraries = ['cspice'], + include_dirs=[os.path.join(CSPICE_SRC, 'include'),], + library_dirs=[os.path.join(CSPICE_SRC, 'lib'),], + define_macros=[#('MSDOS', None), + ('_COMPLEX_DEFINED', None), + ('OMIT_BLANK_CC', None)], +) + try: build_cspice() make_spice_module() @@ -96,7 +122,7 @@ def set_build_paths(): version = '1.0', description = 'Spice Wrapper Module', packages = ['spice'], - ext_modules = [module1] + ext_modules = [spice_custom] ) finally: cleanup() diff --git a/spicemodule_custom.c b/spicemodule_custom.c new file mode 100644 index 0000000..aa57053 --- /dev/null +++ b/spicemodule_custom.c @@ -0,0 +1,340 @@ +/* + * spicemodule_cust.om.c + * + * Created on: Jun 24, 2012 + * Author: Daddy + */ + +#include "pyspice.h" +#include +#include "spicemodule_custom.h" + +PyObject *SpiceException; + +/* void spkw10_c ( SpiceInt handle,SpiceInt body,SpiceInt center, +ConstSpiceChar * frame,SpiceDouble first,SpiceDouble last, +ConstSpiceChar * segid,ConstSpiceDouble consts [8],SpiceInt n, +ConstSpiceDouble elems [],ConstSpiceDouble epochs [] ); */ +PyObject * spice_spkw10(PyObject *self, PyObject *args) +{ + /* variables for inputs */ + long handle; + long body; + long center; + char * frame; + double first; + double last; + char * segid; + double consts[8]; + long n; + PyObject * elems; + PyObject * epochs; + PyObject * elem_seq; + PyObject * epoch_seq; + PyObject * elem; + int i, j ; + double * c_elems; + double * c_epochs; + + char failed = 0; + + PYSPICE_CHECK_RETURN_STATUS(PyArg_ParseTuple(args, "lllsdds(dddddddd)OO", + &handle, &body, ¢er, &frame, &first, &last, &segid, + &consts[0], &consts[1], &consts[2], &consts[3], &consts[4], + &consts[5], &consts[6], &consts[7], &elems, &epochs)); + + // This will fail. Allocation and translation for elems and epoch. + //spkw10_c(handle, body, center, frame, first, last, segid, consts, n, &elems, &epochs); + /* Need to do the following: + * 1: Allocate memory for elems and epochs + * 2: Populate elems and epochs from the python iterables that get passed in. + */ + elem_seq = PySequence_Fast(elems, "expected a sequence for elems"); + epoch_seq = PySequence_Fast(epochs, "expected a sequence for epochs"); + + n = PySequence_Size(elem_seq); + if (n != PySequence_Size(epoch_seq)) { + // TODO: How to raise an exception?? + printf("Naa na naa na naa\nNa na naa na naa\nYou're gonna _____! "); + return NULL; + } + // How to properly allocate memory?? Am I allowed to use malloc? + // See: http://article.gmane.org/gmane.comp.python.general/424736 + c_elems = (double*) PyMem_Malloc(n * 10 * sizeof(double)); + c_epochs = (double*) PyMem_Malloc(n * sizeof(double)); + + for (i = 0; i < n; i++) { + c_epochs[i] = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(epoch_seq, i)); + elem = PySequence_Fast_GET_ITEM(elem_seq, i); + + for (j = 0 ; j < 10 ; j++) { + c_elems[i*10+j] = PyFloat_AsDouble(PySequence_Fast_GET_ITEM(elem, j)); + } + + /* DON'T DECREF item here */ + //Py_DECREF(elem); + //Py_DECREF(epoch); + } + + Py_DECREF(elem_seq); + Py_DECREF(epoch_seq); + + spkw10_c(handle, body, center, frame, first, last, segid, consts, n, + c_elems, c_epochs); + + PyMem_Free(c_elems); + PyMem_Free(c_epochs); + + PYSPICE_CHECK_FAILED; + + if(failed) { + return NULL; + } + + + Py_INCREF(Py_None); + return Py_None; +} +PyDoc_STRVAR(spkw10_doc, "-Abstract\n\n Write an SPK type 10 segment to the DAF open and attached to\n the input handle.\n\n-Brief_I/O\n\n VARIABLE I/O DESCRIPTION\n -------- --- --------------------------------------------------\n handle I The handle of a DAF file open for writing.\n body I The NAIF ID code for the body of the segment.\n center I The center of motion for body.\n frame I The reference frame for this segment.\n first I The first epoch for which the segment is valid.\n last I The last epoch for which the segment is valid.\n segid I The string to use for segment identifier.\n consts I The array of geophysical constants for the segment\n # REMOVED: n I The number of element/epoch pairs to be stored\n elems I The collection of \"two-line\" element sets.\n epochs I The epochs associated with the element sets.\n\n-Detailed_Output\n\n None. The data input is stored in an SPK segment in the\n DAF connected to the input handle.\n\n"); + +/* void getelm_c ( SpiceInt frstyr, + SpiceInt lineln, + const void * lines, + SpiceDouble * epoch, + SpiceDouble * elems ) */ +PyObject * spice_getelm(PyObject *self, PyObject *args) +{ + /* variables for inputs */ + long firstyr; + long lineln; + long line1_len, line2_len; + char * line1; + char * line2; + char * f_lines; + size_t bufsize; + + double elems[10]; + double epoch; + + char failed = 0; + + PYSPICE_CHECK_RETURN_STATUS(PyArg_ParseTuple(args, "l(ss)", + &firstyr, &line1, &line2)); + + // How to properly allocate memory?? Am I allowed to use malloc? + // See: http://article.gmane.org/gmane.comp.python.general/424736 + line1_len = strlen(line1); + line2_len = strlen(line2); + + bufsize = (size_t) 2 + line1_len + line2_len; + f_lines = PyMem_Malloc(bufsize); + + if (f_lines == NULL) { + return PyErr_NoMemory(); + } + + // Offset to the start of the second line. + lineln = line1_len+2; + // Add 1 to line_len to be sure we copy the null too. + strncpy(f_lines, line1, line1_len+2); + strncpy(f_lines+lineln, line2, line2_len+2); + getelm_c(firstyr, lineln, f_lines, &epoch, elems); + + PyMem_Free(f_lines); + + PYSPICE_CHECK_FAILED; + + if(failed) { + return NULL; + } + + PyObject *returnVal = Py_BuildValue("d(dddddddddd)", epoch,\ + elems[0], elems[1], elems[2],\ + elems[3], elems[4], elems[5],\ + elems[6], elems[7], elems[8],\ + elems[9] ); + return returnVal; + + + Py_INCREF(Py_None); + return Py_None; +} +PyDoc_STRVAR(getelm_doc, "-Brief_I/O\ + \ + VARIABLE I/O DESCRIPTION\ + -------- --- --------------------------------------------------\ + frstyr I Year of earliest representable two-line elements.\ + lineln I Length of strings in lines array.\ + lines I A pair of ''lines'' containing two-line elements.\ + epoch O The epoch of the elements in seconds past J2000. \ + elems O The elements converted to SPICE units.\ +\ +-Detailed_Input\ +\ + frstyr is the first year possible for two line elements. Since\ + two line elements allow only two digits for the year, some\ + conventions must be followed concerning which century the\ + two digits refer to. frstyr is the year of the earliest\ + representable elements. The two-digit year is mapped to\ + the year in the interval from frstyr to frstyr + 99 that\ + has the same last two digits as the two digit year in the\ + element set. For example if frstyr is set to 1960 then\ + the two digit years are mapped as shown in the table\ + below:\ +\ + Two-line Maps to\ + element year\ +\ + 00 2000\ + 01 2001\ + 02 2002\ + . .\ + . .\ + . .\ + 58 2058 \ + 59 2059\ + -------------------- \ + 60 1960\ + 61 1961\ + 62 1962\ + . .\ + . .\ + . .\ + 99 1999 \ +\ + Note that if Space Command should decide to represent\ + years in 21st century as 100 + the last two digits of the\ + year (for example: 2015 is represented as 115) instead of\ + simply dropping the first two digits of the year, this\ + routine will correctly map the year as long as you set\ + frstyr to some value between 1900 and 1999.\ +\ + lines is a pair of lines of text that comprise a Space command\ + ``two-line element'' set. lines should be declared\ +\ + SpiceChar lines[2][lineln];\ +\ + These text lines should be the same as they are presented\ + in the two-line element files available from Space\ + Command (formerly NORAD). Below is an example of a\ + two-line set for TOPEX.\ +\ + TOPEX\ + 1 22076U 92052A 97173.53461370 -.00000038 00000-0 10000-3 0 594\ + 2 22076 66.0378 163.4372 0008359 278.7732 81.2337 12.80930736227550\ +\ +\ +\ +\ +-Detailed_Output\ +\ + epoch is the epoch of the two line elements supplied via\ + the input array lines. Epoch is returned in TDB\ + seconds past J2000.\ +\ + elems is an array containing the elements from the two line\ + set supplied via the array lines. The elements are\ + in units suitable for use by the CSPICE routine\ + ev2lin_. \ +\ + Also note that the elements XNDD6O and BSTAR\ + incorporate the exponential factor present in the\ + input two line elements in LINES. (See particulars\ + below.\ +\ + ELEMS [ 0 ] = XNDT2O in radians/minute**2\ + ELEMS [ 1 ] = XNDD6O in radians/minute**3\ + ELEMS [ 2 ] = BSTAR\ + ELEMS [ 3 ] = XINCL in radians\ + ELEMS [ 4 ] = XNODEO in radians\ + ELEMS [ 5 ] = EO\ + ELEMS [ 6 ] = OMEGAO in radians\ + ELEMS [ 7 ] = XMO in radians\ + ELEMS [ 8 ] = XNO in radians/minute\ + ELEMS [ 9 ] = EPOCH of the elements in seconds\ + past ephemeris epoch J2000.\ +\ +-Parameters\ +\ + None.\ +\ +-Exceptions\ +\ + No checking of the inputs is performed in this routine. However, this\ + routine does call other CSPICE routines. If one of these routines\ + detects an error it will diagnose it and signal an error.\ +\ +-Files\ +\ + You must have loaded a SPICE leapseconds kernel into the\ + kernel pool prior to caling this routine.\ +\ +-Particulars\ +\ + This routine parses a Space Command Two-line element set and returns\ + the orbital elements properly scaled and in units suitable for use\ + by other SPICE software. Input elements look like the following\ +\ +---------------------------------------------------------------------\ +1 22076U 92052A 97173.53461370 -.00000038 00000-0 10000-3 0 594\ +2 22076 66.0378 163.4372 0008359 278.7732 81.2337 12.80930736227550\ +---------------------------------------------------------------------\ +^\ +123456789012345678901234567890123456789012345678901234567890123456789\ + 1 2 3 4 5 6\ +\ + The ``raw'' elements in the first and second lines are marked below.\ + Note that in several instances exponents and decimal points are\ + implied. Also note that input units are degrees, degrees/day**n and\ + revolutions/day.\ +\ +\ + DAY OF YEAR NDD60 BSTAR\ + vvvvvvvvvvvv vvvvvv vvvvvv \ +---------------------------------------------------------------------\ +1 22076U 92052A 97173.53461370 -.00000038 00000-0 10000-3 0 594\ +---------------------------------------------------------------------\ + ^^ ^^^^^^^^^^ ^^ ^^\ + YEAR NDT20 IEXP IBEXP\ +\ +\ +\ + The ``raw'' elements in the second line are marked below\ + NODE0 OMEGA N0\ + vvvvvvvv vvvvvvvv vvvvvvvvvvv\ +---------------------------------------------------------------------\ +2 22076 66.0378 163.4372 0008359 278.7732 81.2337 12.80930736227550\ +---------------------------------------------------------------------\ + ^^^^^^^^ ^^^^^^^ ^^^^^^^^\ + Inclination Eccentricity M0\ +\ + This routine extracts these values ``inserts'' the implied\ + decimal points and exponents and then converts the inputs\ + to units of radians, radians/minute, radians/minute**2, and\ + radians/minute**3"); + +/* +PyMethodDef custom_methods[] = { + {"spkw10", spice_spkw10, METH_VARARGS, spkw10_doc}, + {"getelm", spice_getelm, METH_VARARGS, getelm_doc}, + {NULL, NULL}, + +}; + + +void init_spice(PyObject *self) +{ + PyObject *m = NULL; + + m = Py_InitModule("_spice", custom_methods); + + // on't allow an exception to stop execution + erract_c("SET", 0, "RETURN"); + errdev_c("SET", 0, "NULL"); + + SpiceException = PyErr_NewException("_spice.SpiceException", PyExc_Exception, NULL); + Py_INCREF(SpiceException); + + PyModule_AddObject(m, "SpiceException", SpiceException); +} +*/ diff --git a/spicemodule_custom.h b/spicemodule_custom.h new file mode 100644 index 0000000..33a59c9 --- /dev/null +++ b/spicemodule_custom.h @@ -0,0 +1,9 @@ + + +#ifndef __PYSPICE_CUSTOM_H__ +#define __PYSPICE_CUSTOM_H__ 1 + +PyObject * spice_spkw10(PyObject *self, PyObject *args); +PyObject * spice_getelm(PyObject *self, PyObject *args); + +#endif /* __PYSPICE_H__ */