Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision b76191ecef8cfe8022c15b0596dd0ee9afdf8abd)
+++ sasmodels/compare.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -91,5 +91,5 @@
 
     === precision options ===
-    -calc=default uses the default calcution precision
+    -engine=default uses the default calcution precision
     -single/-double/-half/-fast sets an OpenCL calculation engine
     -single!/-double!/-quad! sets an OpenMP calculation engine
@@ -110,5 +110,5 @@
 vary from 64-bit to 128-bit, with 80-bit floats being common (1e-19 precision).
 On unix and mac you may need single quotes around the DLL computation
-engines, such as -calc='single!,double!' since !, is treated as a history
+engines, such as -engine='single!,double!' since !, is treated as a history
 expansion request in the shell.
 
@@ -122,5 +122,5 @@
 
     # compare single and double precision calculation for a barbell
-    sascomp barbell -calc=single,double
+    sascomp barbell -engine=single,double
 
     # generate 10 random lorentz models, with seed=27
@@ -131,5 +131,5 @@
 
     # model timing test requires multiple evals to perform the estimate
-    sascomp pringle -calc=single,double -timing=100,100 -noplot
+    sascomp pringle -engine=single,double -timing=100,100 -noplot
 """
 
@@ -1017,5 +1017,5 @@
 
     # Precision options
-    'calc=',
+    'engine=',
     'half', 'fast', 'single', 'double', 'single!', 'double!', 'quad!',
     'sasview',  # TODO: remove sasview 3.x support
@@ -1167,5 +1167,5 @@
         elif arg.startswith('-title='):    opts['title'] = arg[7:]
         elif arg.startswith('-data='):     opts['datafile'] = arg[6:]
-        elif arg.startswith('-calc='):     opts['engine'] = arg[6:]
+        elif arg.startswith('-engine='):   opts['engine'] = arg[8:]
         elif arg.startswith('-neval='):    opts['evals'] = arg[7:]
         elif arg == '-random':  opts['seed'] = np.random.randint(1000000)
Index: sasmodels/details.py
===================================================================
--- sasmodels/details.py	(revision f39759c773168cc38c66528d0c9c86a7957986fc)
+++ sasmodels/details.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -219,8 +219,8 @@
 
 ZEROS = tuple([0.]*31)
-def make_kernel_args(kernel, pairs):
+def make_kernel_args(kernel, mesh):
     # type: (Kernel, Tuple[List[np.ndarray], List[np.ndarray]]) -> Tuple[CallDetails, np.ndarray, bool]
     """
-    Converts (value, weight) pairs into parameters for the kernel call.
+    Converts (value, dispersity, weight) for each parameter into kernel pars.
 
     Returns a CallDetails object indicating the polydispersity, a data object
@@ -231,15 +231,15 @@
     npars = kernel.info.parameters.npars
     nvalues = kernel.info.parameters.nvalues
-    scalars = [(v[0] if len(v) else np.NaN) for v, w in pairs]
+    scalars = [value for value, dispersity, weight in mesh]
     # skipping scale and background when building values and weights
-    values, weights = zip(*pairs[2:npars+2]) if npars else ((), ())
-    weights = correct_theta_weights(kernel.info.parameters, values, weights)
+    values, dispersity, weights = zip(*mesh[2:npars+2]) if npars else ((), (), ())
+    #weights = correct_theta_weights(kernel.info.parameters, values, weights)
     length = np.array([len(w) for w in weights])
     offset = np.cumsum(np.hstack((0, length)))
     call_details = make_details(kernel.info, length, offset[:-1], offset[-1])
     # Pad value array to a 32 value boundaryd
-    data_len = nvalues + 2*sum(len(v) for v in values)
+    data_len = nvalues + 2*sum(len(v) for v in dispersity)
     extra = (32 - data_len%32)%32
-    data = np.hstack((scalars,) + values + weights + ZEROS[:extra])
+    data = np.hstack((scalars,) + dispersity + weights + ZEROS[:extra])
     data = data.astype(kernel.dtype)
     is_magnetic = convert_magnetism(kernel.info.parameters, data)
@@ -294,16 +294,18 @@
 
 
-def dispersion_mesh(model_info, pars):
+def dispersion_mesh(model_info, mesh):
     # type: (ModelInfo) -> Tuple[List[np.ndarray], List[np.ndarray]]
     """
     Create a mesh grid of dispersion parameters and weights.
 
-    pars is a list of pairs (values, weights), where the values are the
-    individual parameter values at which to evaluate the polydispersity
-    distribution and weights are the weights associated with each value.
+    *mesh* is a list of (value, dispersity, weights), where the values
+    are the individual parameter values, and (dispersity, weights) is
+    the distribution of parameter values.
 
     Only the volume parameters should be included in this list.  Orientation
     parameters do not affect the calculation of effective radius or volume
-    ratio.
+    ratio.  This is convenient since it avoids the distinction between
+    value and dispersity that is present in orientation parameters but not
+    shape parameters.
 
     Returns [p1,p2,...],w where pj is a vector of values for parameter j
@@ -311,9 +313,9 @@
     parameter set in the vector.
     """
-    value, weight = zip(*pars)
+    value, dispersity, weight = zip(*mesh)
     #weight = [w if len(w)>0 else [1.] for w in weight]
     weight = np.vstack([v.flatten() for v in meshgrid(*weight)])
     weight = np.prod(weight, axis=0)
-    value = [v.flatten() for v in meshgrid(*value)]
+    dispersity = [v.flatten() for v in meshgrid(*dispersity)]
     lengths = [par.length for par in model_info.parameters.kernel_parameters
                if par.type == 'volume']
@@ -322,7 +324,7 @@
         offset = 0
         for n in lengths:
-            pars.append(np.vstack(value[offset:offset+n])
-                        if n > 1 else value[offset])
+            pars.append(np.vstack(dispersity[offset:offset+n])
+                        if n > 1 else dispersity[offset])
             offset += n
-        value = pars
-    return value, weight
+        dispersity = pars
+    return dispersity, weight
Index: sasmodels/direct_model.py
===================================================================
--- sasmodels/direct_model.py	(revision d1ff3a5d39af7e61166a61d76c48aa3a86a6e216)
+++ sasmodels/direct_model.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -66,9 +66,8 @@
 
     #print("pars",[p.id for p in parameters.call_parameters])
-    vw_pairs = [(get_weights(p, pars) if active(p.name)
-                 else ([pars.get(p.name, p.default)], [1.0]))
-                for p in parameters.call_parameters]
-
-    call_details, values, is_magnetic = make_kernel_args(calculator, vw_pairs)
+    mesh = [get_weights(p, pars, active(p.name))
+            for p in parameters.call_parameters]
+
+    call_details, values, is_magnetic = make_kernel_args(calculator, mesh)
     #print("values:", values)
     return calculator(call_details, values, cutoff, is_magnetic)
@@ -130,5 +129,5 @@
 
 
-def get_weights(parameter, values):
+def get_weights(parameter, values, active=True):
     # type: (Parameter, Dict[str, float]) -> Tuple[np.ndarray, np.ndarray]
     """
@@ -140,15 +139,19 @@
     """
     value = float(values.get(parameter.name, parameter.default))
-    relative = parameter.relative_pd
-    limits = parameter.limits
-    disperser = values.get(parameter.name+'_pd_type', 'gaussian')
     npts = values.get(parameter.name+'_pd_n', 0)
     width = values.get(parameter.name+'_pd', 0.0)
-    nsigma = values.get(parameter.name+'_pd_nsigma', 3.0)
-    if npts == 0 or width == 0:
-        return [value], [1.0]
-    value, weight = weights.get_weights(
-        disperser, npts, width, nsigma, value, limits, relative)
-    return value, weight / np.sum(weight)
+    if npts == 0 or width == 0 or not active:
+        # Note: orientation parameters have the viewing angle as the parameter
+        # value and the jitter in the distribution, so be sure to set the
+        # empty pd for orientation parameters to 0.
+        pd = [value if parameter.type != 'orientation' else 0.0], [1.0]
+    else:
+        relative = parameter.relative_pd
+        limits = parameter.limits
+        disperser = values.get(parameter.name+'_pd_type', 'gaussian')
+        nsigma = values.get(parameter.name+'_pd_nsigma', 3.0)
+        pd = weights.get_weights(disperser, npts, width, nsigma,
+                                value, limits, relative)
+    return value, pd[0], pd[1]
 
 
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision 30b60d21e384fd9fda8e281e787ff2869925e10d)
+++ sasmodels/generate.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -714,5 +714,5 @@
     # Define the parameter table
     # TODO: plug in current line number
-    source.append('#line 542 "sasmodels/generate.py"')
+    source.append('#line 716 "sasmodels/generate.py"')
     source.append("#define PARAMETER_TABLE \\")
     source.append("\\\n".join(p.as_definition()
@@ -730,16 +730,20 @@
     source.append(call_volume)
 
-    refs = ["_q[_i]"] + _call_pars("_v.", partable.iq_parameters)
-    call_iq = "#define CALL_IQ(_q,_i,_v) Iq(%s)" % (",".join(refs))
+    model_refs = _call_pars("_v.", partable.iq_parameters)
+    pars = ",".join(["_q"] + model_refs)
+    call_iq = "#define CALL_IQ(_q, _v) Iq(%s)" % pars
     if _have_Iqxy(user_code) or isinstance(model_info.Iqxy, str):
-        # Call 2D model
-        refs = ["_q[2*_i]", "_q[2*_i+1]"] + _call_pars("_v.", partable.iqxy_parameters)
-        call_iqxy = "#define CALL_IQ(_q,_i,_v) Iqxy(%s)" % (",".join(refs))
+        if partable.is_asymmetric:
+            pars = ",".join(["_qa", "_qb", "_qc"] + model_refs)
+            call_iqxy = "#define CALL_IQ_ABC(_qa,_qb,_qc,_v) Iqxy(%s)" % pars
+            clear_iqxy = "#undef CALL_IQ_ABC"
+        else:
+            pars = ",".join(["_qa", "_qc"] + model_refs)
+            call_iqxy = "#define CALL_IQ_AC(_qa,_qc,_v) Iqxy(%s)" % pars
+            clear_iqxy = "#undef CALL_IQ_AC"
     else:
-        # Call 1D model with sqrt(qx^2 + qy^2)
-        #warnings.warn("Creating Iqxy = Iq(sqrt(qx^2 + qy^2))")
-        # still defined:: refs = ["q[i]"] + _call_pars("v", iq_parameters)
-        pars_sqrt = ["sqrt(_q[2*_i]*_q[2*_i]+_q[2*_i+1]*_q[2*_i+1])"] + refs[1:]
-        call_iqxy = "#define CALL_IQ(_q,_i,_v) Iq(%s)" % (",".join(pars_sqrt))
+        pars = ",".join(["_qa"] + model_refs)
+        call_iqxy = "#define CALL_IQ_A(_qa,_v) Iq(%s)" % pars
+        clear_iqxy = "#undef CALL_IQ_A"
 
     magpars = [k-2 for k, p in enumerate(partable.call_parameters)
@@ -757,6 +761,6 @@
     # TODO: allow mixed python/opencl kernels?
 
-    ocl = kernels(ocl_code, call_iq, call_iqxy, model_info.name)
-    dll = kernels(dll_code, call_iq, call_iqxy, model_info.name)
+    ocl = kernels(ocl_code, call_iq, call_iqxy, clear_iqxy, model_info.name)
+    dll = kernels(dll_code, call_iq, call_iqxy, clear_iqxy, model_info.name)
     result = {
         'dll': '\n'.join(source+dll[0]+dll[1]+dll[2]),
@@ -767,5 +771,5 @@
 
 
-def kernels(kernel, call_iq, call_iqxy, name):
+def kernels(kernel, call_iq, call_iqxy, clear_iqxy, name):
     # type: ([str,str], str, str, str) -> List[str]
     code = kernel[0]
@@ -787,5 +791,5 @@
         '#line 1 "%s Iqxy"' % path,
         code,
-        "#undef CALL_IQ",
+        clear_iqxy,
         "#undef KERNEL_NAME",
     ]
@@ -798,6 +802,6 @@
         '#line 1 "%s Imagnetic"' % path,
         code,
+        clear_iqxy,
         "#undef MAGNETIC",
-        "#undef CALL_IQ",
         "#undef KERNEL_NAME",
     ]
Index: sasmodels/kernel_header.c
===================================================================
--- sasmodels/kernel_header.c	(revision bb4b50990533a15ceda4443988db938e13a28a2b)
+++ sasmodels/kernel_header.c	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -87,4 +87,7 @@
 
 #if defined(NEED_EXPM1)
+   // TODO: precision is a half digit lower than numpy on mac in [1e-7, 0.5]
+   // Run "explore/precision.py sas_expm1" to see this (may have to fiddle
+   // the xrange for log to see the complete range).
    static SAS_DOUBLE expm1(SAS_DOUBLE x_in) {
       double x = (double)x_in;  // go back to float for single precision kernels
@@ -147,67 +150,2 @@
 inline double cube(double x) { return x*x*x; }
 inline double sas_sinx_x(double x) { return x==0 ? 1.0 : sin(x)/x; }
-
-// To rotate from the canonical position to theta, phi, psi, first rotate by
-// psi about the major axis, oriented along z, which is a rotation in the
-// detector plane xy. Next rotate by theta about the y axis, aligning the major
-// axis in the xz plane. Finally, rotate by phi in the detector plane xy.
-// To compute the scattering, undo these rotations in reverse order:
-//     rotate in xy by -phi, rotate in xz by -theta, rotate in xy by -psi
-// The returned q is the length of the q vector and (xhat, yhat, zhat) is a unit
-// vector in the q direction.
-// To change between counterclockwise and clockwise rotation, change the
-// sign of phi and psi.
-
-#if 1
-//think cos(theta) should be sin(theta) in new coords, RKH 11Jan2017
-#define ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sn, cn) do { \
-    SINCOS(phi*M_PI_180, sn, cn); \
-    q = sqrt(qx*qx + qy*qy); \
-    cn = (q==0. ? 1.0 : (cn*qx + sn*qy)/q * sin(theta*M_PI_180));  \
-    sn = sqrt(1 - cn*cn); \
-    } while (0)
-#else
-// SasView 3.x definition of orientation
-#define ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sn, cn) do { \
-    SINCOS(theta*M_PI_180, sn, cn); \
-    q = sqrt(qx*qx + qy*qy);\
-    cn = (q==0. ? 1.0 : (cn*cos(phi*M_PI_180)*qx + sn*qy)/q); \
-    sn = sqrt(1 - cn*cn); \
-    } while (0)
-#endif
-
-#if 1
-#define ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, xhat, yhat, zhat) do { \
-    q = sqrt(qx*qx + qy*qy); \
-    const double qxhat = qx/q; \
-    const double qyhat = qy/q; \
-    double sin_theta, cos_theta; \
-    double sin_phi, cos_phi; \
-    double sin_psi, cos_psi; \
-    SINCOS(theta*M_PI_180, sin_theta, cos_theta); \
-    SINCOS(phi*M_PI_180, sin_phi, cos_phi); \
-    SINCOS(psi*M_PI_180, sin_psi, cos_psi); \
-    xhat = qxhat*(-sin_phi*sin_psi + cos_theta*cos_phi*cos_psi) \
-         + qyhat*( cos_phi*sin_psi + cos_theta*sin_phi*cos_psi); \
-    yhat = qxhat*(-sin_phi*cos_psi - cos_theta*cos_phi*sin_psi) \
-         + qyhat*( cos_phi*cos_psi - cos_theta*sin_phi*sin_psi); \
-    zhat = qxhat*(-sin_theta*cos_phi) \
-         + qyhat*(-sin_theta*sin_phi); \
-    } while (0)
-#else
-// SasView 3.x definition of orientation
-#define ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_alpha, cos_mu, cos_nu) do { \
-    q = sqrt(qx*qx + qy*qy); \
-    const double qxhat = qx/q; \
-    const double qyhat = qy/q; \
-    double sin_theta, cos_theta; \
-    double sin_phi, cos_phi; \
-    double sin_psi, cos_psi; \
-    SINCOS(theta*M_PI_180, sin_theta, cos_theta); \
-    SINCOS(phi*M_PI_180, sin_phi, cos_phi); \
-    SINCOS(psi*M_PI_180, sin_psi, cos_psi); \
-    cos_alpha = cos_theta*cos_phi*qxhat + sin_theta*qyhat; \
-    cos_mu = (-sin_theta*cos_psi*cos_phi - sin_psi*sin_phi)*qxhat + cos_theta*cos_psi*qyhat; \
-    cos_nu = (-cos_phi*sin_psi*sin_theta + sin_phi*cos_psi)*qxhat + sin_psi*cos_theta*qyhat; \
-    } while (0)
-#endif
Index: sasmodels/kernel_iq.c
===================================================================
--- sasmodels/kernel_iq.c	(revision d4c33d66aa792e691ad5eb91b36d3e854e88f383)
+++ sasmodels/kernel_iq.c	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -25,5 +25,5 @@
     int32_t num_weights;        // total length of the weights vector
     int32_t num_active;         // number of non-trivial pd loops
-    int32_t theta_par;          // id of spherical correction variable (not used)
+    int32_t theta_par;          // id of first orientation variable
 } ProblemDetails;
 
@@ -69,6 +69,136 @@
     return sld + perp*p;
 }
-
-#endif // MAGNETIC
+//#endif // MAGNETIC
+
+// TODO: way too hackish
+// For the 1D kernel, CALL_IQ_[A,AC,ABC] and MAGNETIC are not defined
+// so view_direct *IS NOT* included
+// For the 2D kernel, CALL_IQ_[A,AC,ABC] is defined but MAGNETIC is not
+// so view_direct *IS* included
+// For the magnetic kernel, CALL_IQ_[A,AC,ABC] is defined, but so is MAGNETIC
+// so view_direct *IS NOT* included
+#else // !MAGNETIC
+
+// ===== Implement jitter in orientation =====
+// To change the definition of the angles, run explore/angles.py, which
+// uses sympy to generate the equations.
+
+#if defined(CALL_IQ_AC) // oriented symmetric
+static double
+view_direct(double qx, double qy,
+             double theta, double phi,
+             ParameterTable table)
+{
+    double sin_theta, cos_theta;
+    double sin_phi, cos_phi;
+
+    // reverse view
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi);
+    const double qa = qx*cos_phi*cos_theta + qy*sin_phi*cos_theta;
+    const double qb = -qx*sin_phi + qy*cos_phi;
+    const double qc = qx*sin_theta*cos_phi + qy*sin_phi*sin_theta;
+
+    // reverse jitter after view
+    SINCOS(table.theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(table.phi*M_PI_180, sin_phi, cos_phi);
+    const double dqc = qa*sin_theta - qb*sin_phi*cos_theta + qc*cos_phi*cos_theta;
+
+    // Indirect calculation of qab, from qab^2 = |q|^2 - qc^2
+    const double dqa = sqrt(-dqc*dqc + qx*qx + qy*qy);
+
+    return CALL_IQ_AC(dqa, dqc, table);
+}
+
+#elif defined(CALL_IQ_ABC) // oriented asymmetric
+
+static double
+view_direct(double qx, double qy,
+             double theta, double phi, double psi,
+             ParameterTable table)
+{
+    double sin_theta, cos_theta;
+    double sin_phi, cos_phi;
+    double sin_psi, cos_psi;
+
+    // reverse view
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(psi*M_PI_180, sin_psi, cos_psi);
+    const double qa = qx*(sin_phi*sin_psi + cos_phi*cos_psi*cos_theta) + qy*(sin_phi*cos_psi*cos_theta - sin_psi*cos_phi);
+    const double qb = qx*(-sin_phi*cos_psi + sin_psi*cos_phi*cos_theta) + qy*(sin_phi*sin_psi*cos_theta + cos_phi*cos_psi);
+    const double qc = qx*sin_theta*cos_phi + qy*sin_phi*sin_theta;
+
+    // reverse jitter after view
+    SINCOS(table.theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(table.phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(table.psi*M_PI_180, sin_psi, cos_psi);
+    const double dqa = qa*cos_psi*cos_theta + qb*(sin_phi*sin_theta*cos_psi - sin_psi*cos_phi) + qc*(-sin_phi*sin_psi - sin_theta*cos_phi*cos_psi);
+    const double dqb = qa*sin_psi*cos_theta + qb*(sin_phi*sin_psi*sin_theta + cos_phi*cos_psi) + qc*(sin_phi*cos_psi - sin_psi*sin_theta*cos_phi);
+    const double dqc = qa*sin_theta - qb*sin_phi*cos_theta + qc*cos_phi*cos_theta;
+
+    return CALL_IQ_ABC(dqa, dqb, dqc, table);
+}
+/* TODO: use precalculated jitter for faster 2D calcs on DLL.
+static void
+view_precalc(
+    double theta, double phi, double psi,
+    ParameterTable table,
+    double *R11, double *R12, double *R21,
+    double *R22, double *R31, double *R32)
+{
+    double sin_theta, cos_theta;
+    double sin_phi, cos_phi;
+    double sin_psi, cos_psi;
+
+    // reverse view matrix
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(psi*M_PI_180, sin_psi, cos_psi);
+    const double V11 = sin_phi*sin_psi + cos_phi*cos_psi*cos_theta;
+    const double V12 = sin_phi*cos_psi*cos_theta - sin_psi*cos_phi;
+    const double V21 = -sin_phi*cos_psi + sin_psi*cos_phi*cos_theta;
+    const double V22 = sin_phi*sin_psi*cos_theta + cos_phi*cos_psi;
+    const double V31 = sin_theta*cos_phi;
+    const double V32 = sin_phi*sin_theta;
+
+    // reverse jitter matrix
+    SINCOS(table.theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(table.phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(table.psi*M_PI_180, sin_psi, cos_psi);
+    const double J11 = cos_psi*cos_theta;
+    const double J12 = sin_phi*sin_theta*cos_psi - sin_psi*cos_phi;
+    const double J13 = -sin_phi*sin_psi - sin_theta*cos_phi*cos_psi;
+    const double J21 = sin_psi*cos_theta;
+    const double J22 = sin_phi*sin_psi*sin_theta + cos_phi*cos_psi;
+    const double J23 = sin_phi*cos_psi - sin_psi*sin_theta*cos_phi;
+    const double J31 = sin_theta;
+    const double J32 = -sin_phi*cos_theta;
+    const double J33 = cos_phi*cos_theta;
+
+    // reverse matrix
+    *R11 = J11*V11 + J12*V21 + J13*V31;
+    *R12 = J11*V12 + J12*V22 + J13*V32;
+    *R21 = J21*V11 + J22*V21 + J23*V31;
+    *R22 = J21*V12 + J22*V22 + J23*V32;
+    *R31 = J31*V11 + J32*V21 + J33*V31;
+    *R32 = J31*V12 + J32*V22 + J33*V32;
+}
+
+static double
+view_apply(double qx, double qy,
+    double R11, double R12, double R21, double R22, double R31, double R32,
+    ParameterTable table)
+{
+    const double dqa = R11*qx + R12*qy;
+    const double dqb = R21*qx + R22*qy;
+    const double dqc = R31*qx + R32*qy;
+
+    CALL_IQ_ABC(dqa, dqb, dqc, table);
+}
+*/
+#endif
+
+#endif // !MAGNETIC
 
 kernel
@@ -105,4 +235,13 @@
   SINCOS(-values[NUM_PARS+4]*M_PI_180, sin_mspin, cos_mspin);
 #endif // MAGNETIC
+
+#if defined(CALL_IQ_AC) // oriented symmetric
+  const double theta = values[details->theta_par+2];
+  const double phi = values[details->theta_par+3];
+#elif defined(CALL_IQ_ABC) // oriented asymmetric
+  const double theta = values[details->theta_par+2];
+  const double phi = values[details->theta_par+3];
+  const double psi = values[details->theta_par+4];
+#endif
 
   // Fill in the initial variables
@@ -275,11 +414,27 @@
                   }
                   #endif
-                  scattering += CALL_IQ(q, q_index, local_values.table);
+#  if defined(CALL_IQ_A) // unoriented
+                  scattering += CALL_IQ_A(sqrt(qsq), local_values.table);
+#  elif defined(CALL_IQ_AC) // oriented symmetric
+                  scattering += view_direct(qx, qy, theta, phi, local_values.table);
+#  elif defined(CALL_IQ_ABC) // oriented asymmetric
+                  scattering += view_direct(qx, qy, theta, phi, psi, local_values.table);
+#  endif
                 }
               }
             }
           }
-#else  // !MAGNETIC
-          const double scattering = CALL_IQ(q, q_index, local_values.table);
+#elif defined(CALL_IQ) // 1d, not MAGNETIC
+          const double scattering = CALL_IQ(q[q_index], local_values.table);
+#else  // 2d data, not magnetic
+          const double qx = q[2*q_index];
+          const double qy = q[2*q_index+1];
+#  if defined(CALL_IQ_A) // unoriented
+          const double scattering = CALL_IQ_A(sqrt(qx*qx+qy*qy), local_values.table);
+#  elif defined(CALL_IQ_AC) // oriented symmetric
+          const double scattering = view_direct(qx, qy, theta, phi, local_values.table);
+#  elif defined(CALL_IQ_ABC) // oriented asymmetric
+          const double scattering = view_direct(qx, qy, theta, phi, psi, local_values.table);
+#  endif
 #endif // !MAGNETIC
 //printf("q_index:%d %g %g %g %g\n",q_index, scattering, weight, spherical_correction, weight0);
Index: sasmodels/kernel_iq.cl
===================================================================
--- sasmodels/kernel_iq.cl	(revision d4c33d66aa792e691ad5eb91b36d3e854e88f383)
+++ sasmodels/kernel_iq.cl	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -70,5 +70,78 @@
 }
 
-#endif // MAGNETIC
+//#endif // MAGNETIC
+
+// TODO: way too hackish
+// For the 1D kernel, CALL_IQ_[A,AC,ABC] and MAGNETIC are not defined
+// so view_direct *IS NOT* included
+// For the 2D kernel, CALL_IQ_[A,AC,ABC] is defined but MAGNETIC is not
+// so view_direct *IS* included
+// For the magnetic kernel, CALL_IQ_[A,AC,ABC] is defined, but so is MAGNETIC
+// so view_direct *IS NOT* included
+#else // !MAGNETIC
+
+// ===== Implement jitter in orientation =====
+// To change the definition of the angles, run explore/angles.py, which
+// uses sympy to generate the equations.
+
+#if defined(CALL_IQ_AC) // oriented symmetric
+static double
+view_direct(double qx, double qy,
+             double theta, double phi,
+             ParameterTable table)
+{
+    double sin_theta, cos_theta;
+    double sin_phi, cos_phi;
+
+    // reverse view
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi);
+    const double qa = qx*cos_phi*cos_theta + qy*sin_phi*cos_theta;
+    const double qb = -qx*sin_phi + qy*cos_phi;
+    const double qc = qx*sin_theta*cos_phi + qy*sin_phi*sin_theta;
+
+    // reverse jitter after view
+    SINCOS(table.theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(table.phi*M_PI_180, sin_phi, cos_phi);
+    const double dqc = qa*sin_theta - qb*sin_phi*cos_theta + qc*cos_phi*cos_theta;
+
+    // Indirect calculation of qab, from qab^2 = |q|^2 - qc^2
+    const double dqa = sqrt(-dqc*dqc + qx*qx + qy*qy);
+
+    return CALL_IQ_AC(dqa, dqc, table);
+}
+
+#elif defined(CALL_IQ_ABC) // oriented asymmetric
+
+static double
+view_direct(double qx, double qy,
+             double theta, double phi, double psi,
+             ParameterTable table)
+{
+    double sin_theta, cos_theta;
+    double sin_phi, cos_phi;
+    double sin_psi, cos_psi;
+
+    // reverse view
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(psi*M_PI_180, sin_psi, cos_psi);
+    const double qa = qx*(sin_phi*sin_psi + cos_phi*cos_psi*cos_theta) + qy*(sin_phi*cos_psi*cos_theta - sin_psi*cos_phi);
+    const double qb = qx*(-sin_phi*cos_psi + sin_psi*cos_phi*cos_theta) + qy*(sin_phi*sin_psi*cos_theta + cos_phi*cos_psi);
+    const double qc = qx*sin_theta*cos_phi + qy*sin_phi*sin_theta;
+
+    // reverse jitter after view
+    SINCOS(table.theta*M_PI_180, sin_theta, cos_theta);
+    SINCOS(table.phi*M_PI_180, sin_phi, cos_phi);
+    SINCOS(table.psi*M_PI_180, sin_psi, cos_psi);
+    const double dqa = qa*cos_psi*cos_theta + qb*(sin_phi*sin_theta*cos_psi - sin_psi*cos_phi) + qc*(-sin_phi*sin_psi - sin_theta*cos_phi*cos_psi);
+    const double dqb = qa*sin_psi*cos_theta + qb*(sin_phi*sin_psi*sin_theta + cos_phi*cos_psi) + qc*(sin_phi*cos_psi - sin_psi*sin_theta*cos_phi);
+    const double dqc = qa*sin_theta - qb*sin_phi*cos_theta + qc*cos_phi*cos_theta;
+
+    return CALL_IQ_ABC(dqa, dqb, dqc, table);
+}
+#endif
+
+#endif // !MAGNETIC
 
 kernel
@@ -111,4 +184,13 @@
 #endif // MAGNETIC
 
+#if defined(CALL_IQ_AC) // oriented symmetric
+  const double theta = values[details->theta_par+2];
+  const double phi = values[details->theta_par+3];
+#elif defined(CALL_IQ_ABC) // oriented asymmetric
+  const double theta = values[details->theta_par+2];
+  const double phi = values[details->theta_par+3];
+  const double psi = values[details->theta_par+4];
+#endif
+
   // Fill in the initial variables
   //   values[0] is scale
@@ -215,4 +297,7 @@
 
 //if (q_index == 0) {printf("step:%d of %d, pars:",step,pd_stop); for (int i=0; i < NUM_PARS; i++) printf("p%d=%g ",i, local_values.vector[i]); printf("\n"); }
+//#if defined(CALL_IQ_AC) // oriented symmetric
+//if (q_index == 0) {printf("step:%d of %d, pars:",step,pd_stop); printf("theta=%g dtheta=%g",theta,local_values.table.theta); printf("\n"); }
+//#endif
 
     #ifdef INVALID
@@ -267,11 +352,27 @@
                 }
                 #endif
-                scattering += CALL_IQ(q, q_index, local_values.table);
+#  if defined(CALL_IQ_A) // unoriented
+                scattering += CALL_IQ_A(sqrt(qsq), local_values.table);
+#  elif defined(CALL_IQ_AC) // oriented symmetric
+                scattering += view_direct(qx, qy, theta, phi, local_values.table);
+#  elif defined(CALL_IQ_ABC) // oriented asymmetric
+                scattering += view_direct(qx, qy, theta, phi, psi, local_values.table);
+#  endif
               }
             }
           }
         }
-#else  // !MAGNETIC
-        const double scattering = CALL_IQ(q, q_index, local_values.table);
+#elif defined(CALL_IQ) // 1d, not MAGNETIC
+          const double scattering = CALL_IQ(q[q_index], local_values.table);
+#else  // 2d data, not magnetic
+          const double qx = q[2*q_index];
+          const double qy = q[2*q_index+1];
+#  if defined(CALL_IQ_A) // unoriented
+          const double scattering = CALL_IQ_A(sqrt(qx*qx+qy*qy), local_values.table);
+#  elif defined(CALL_IQ_AC) // oriented symmetric
+          const double scattering = view_direct(qx, qy, theta, phi, local_values.table);
+#  elif defined(CALL_IQ_ABC) // oriented asymmetric
+          const double scattering = view_direct(qx, qy, theta, phi, psi, local_values.table);
+#  endif
 #endif // !MAGNETIC
         this_result += weight0 * scattering;
Index: sasmodels/kernelpy.py
===================================================================
--- sasmodels/kernelpy.py	(revision 3b32f8db57b4509d51d057e24ead69089348a13b)
+++ sasmodels/kernelpy.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -113,6 +113,7 @@
 
         partable = model_info.parameters
-        kernel_parameters = (partable.iqxy_parameters if q_input.is_2d
-                             else partable.iq_parameters)
+        #kernel_parameters = (partable.iqxy_parameters if q_input.is_2d
+        #                     else partable.iq_parameters)
+        kernel_parameters = partable.iq_parameters
         volume_parameters = partable.form_volume_parameters
 
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision 65314f75d3d74de18ada1d98440acf06ab8c4ce3)
+++ sasmodels/modelinfo.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -382,5 +382,5 @@
       with vector parameter p sent as p[].
 
-    * *iqxy_parameters* is the list of parameters to the Iqxy(qx, qy, ...)
+    * [removed] *iqxy_parameters* is the list of parameters to the Iqxy(qx, qy, ...)
       function, with vector parameter p sent as p[].
 
@@ -420,4 +420,5 @@
         # type: (List[Parameter]) -> None
         self.kernel_parameters = parameters
+        self._check_angles()
         self._set_vector_lengths()
 
@@ -438,6 +439,7 @@
         self.iq_parameters = [p for p in self.kernel_parameters
                               if p.type not in ('orientation', 'magnetic')]
-        self.iqxy_parameters = [p for p in self.kernel_parameters
-                                if p.type != 'magnetic']
+        # note: orientation no longer sent to Iqxy, so its the same as
+        #self.iqxy_parameters = [p for p in self.kernel_parameters
+        #                        if p.type != 'magnetic']
         self.form_volume_parameters = [p for p in self.kernel_parameters
                                        if p.type == 'volume']
@@ -461,4 +463,5 @@
         self.has_2d = any(p.type in ('orientation', 'magnetic')
                           for p in self.kernel_parameters)
+        self.is_asymmetric = any(p.name == 'psi' for p in self.kernel_parameters)
         self.magnetism_index = [k for k, p in enumerate(self.call_parameters)
                                 if p.id.startswith('M0:')]
@@ -467,4 +470,27 @@
                          if p.polydisperse and p.type not in ('orientation', 'magnetic'))
         self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse)
+
+    def _check_angles(self):
+        theta = phi = psi = -1
+        for k, p in enumerate(self.kernel_parameters):
+            if p.name == 'theta':
+                theta = k
+                if p.type != 'orientation':
+                    raise TypeError("theta must be an orientation parameter")
+            elif p.name == 'phi':
+                phi = k
+                if p.type != 'orientation':
+                    raise TypeError("phi must be an orientation parameter")
+            elif p.name == 'psi':
+                psi = k
+                if p.type != 'orientation':
+                    raise TypeError("psi must be an orientation parameter")
+        if theta >= 0 and phi >= 0:
+            if phi != theta+1:
+                raise TypeError("phi must follow theta")
+            if psi >= 0 and psi != phi+1:
+                raise TypeError("psi must follow phi")
+        elif theta >= 0 or phi >= 0 or psi >= 0:
+            raise TypeError("oriented shapes must have both theta and phi and maybe psi")
 
     def __getitem__(self, key):
Index: sasmodels/weights.py
===================================================================
--- sasmodels/weights.py	(revision d4c33d66aa792e691ad5eb91b36d3e854e88f383)
+++ sasmodels/weights.py	(revision 8698a0ddea5a155fcc2e3f1085f06a618b46eebf)
@@ -57,5 +57,5 @@
         if not relative:
             # For orientation, the jitter is relative to 0 not the angle
-            #center = 0
+            center = 0
             pass
         if sigma == 0 or self.npts < 2:
@@ -229,5 +229,5 @@
     obj = cls(n, width, nsigmas)
     v, w = obj.get_weights(value, limits[0], limits[1], relative)
-    return v, w
+    return v, w/np.sum(w)
 
 
