diff --git a/docs/reference/specifiers.rst b/docs/reference/specifiers.rst index 6dc4d42b6..83f99f74c 100644 --- a/docs/reference/specifiers.rst +++ b/docs/reference/specifiers.rst @@ -98,10 +98,13 @@ contained in *region* Like :sampref:`in {region}`, but also enforces that the object be entirely contained in the given `Region`. .. _on {region}: +.. _on ({region} | {Object}): +.. _on ({region} | {Object} | {vector}): +.. _on {vector}: .. _on: -on *region* ------------ +on (*region* | *Object* | *vector*) +----------------------------------- **Specifies**: @@ -110,12 +113,10 @@ on *region* **Dependencies**: :prop:`baseOffset` • :prop:`contactTolerance` • :prop:`onDirection` -If :prop:`position` is not already specified with priority 1, positions the *base* of the object uniformly at random in the given `Region`, offset by :prop:`contactTolerance` (to avoid a collision). +If :prop:`position` is not already specified with priority 1, positions the *base* of the object uniformly at random in the given `Region`, on the :prop:`onSurface` of the given `Object`, or with the base of the object at the given vector. The position is always offset by half of :prop:`contactTolerance` (to avoid a collision). The base of the object is determined by adding the object's :prop:`baseOffset` to its :prop:`position`. -Note that while :specifier:`on` can be used with `Region`, `Object` and `Vector`, it cannot be used with a distribution containing anything other than `Region`. When used with an object the base of the object being placed is placed on the target object's `onSurface` and when used with a vector the base of the object being placed is set to that position. - -If instead :prop:`position` has already been specified with priority 1, then its value is modified by projecting it onto the given region. +If instead :prop:`position` has already been specified with priority 1, then its value is modified by projecting it onto the given region (or the :prop:`onSurface` of the given object). Note that this modifying version of the specifier does not accept a vector. More precisely, we find the closest point in the region along :prop:`onDirection` (or its negation [1]_), and place the base of the object at that point. If :prop:`onDirection` is not specified, a default value is inferred from the region. A region can either specify a default value to be used, or for volumes straight up is used and for surfaces the mean of the face normal values is used (weighted by the area of the faces). If the region has a :term:`preferred orientation` (a vector field), :prop:`parentOrientation` is specified to be equal to that orientation at the object’s :prop:`position` (whether or not this specifier is being used as a modifying specifier). diff --git a/docs/syntax_guide.rst b/docs/syntax_guide.rst index 6ed669cbe..c376ad450 100644 --- a/docs/syntax_guide.rst +++ b/docs/syntax_guide.rst @@ -208,8 +208,10 @@ Additional specifiers for the :prop:`position` and :prop:`orientation` propertie - Positions the object uniformly at random in the given Region * - :sampref:`contained in {region}` - Positions the object uniformly at random entirely contained in the given Region - * - :sampref:`on {region}` - - Positions the base of the object uniformly at random in the given Region, or modifies the position so that the base is in the Region. + * - :sampref:`on {vector}` + - Positions the base of the object at the given global coordinates + * - :sampref:`on ({region} | {Object})` + - Positions the object uniformly at random or modifies the position so that base of the Object is in the given Region/on the given Object. * - :sampref:`offset by {vector}` - Positions the object at the given coordinates in the local coordinate system of ego (which must already be defined) * - :sampref:`offset along {direction} by {vector}` diff --git a/src/scenic/core/type_support.py b/src/scenic/core/type_support.py index ac6350b7c..7b8c6cb81 100644 --- a/src/scenic/core/type_support.py +++ b/src/scenic/core/type_support.py @@ -154,12 +154,12 @@ def canCoerceType(typeA, typeB): return issubclass(typeA, typeB) -def canCoerce(thing, ty): +def canCoerce(thing, ty, exact=False): """Can this value be coerced into the given type?""" tt = underlyingType(thing) if canCoerceType(tt, ty): return True - elif isinstance(thing, Distribution): + elif (not exact) and isinstance(thing, Distribution): return True # fall back on type-checking at runtime else: return False diff --git a/src/scenic/syntax/veneer.py b/src/scenic/syntax/veneer.py index 2e7650efa..51fd84918 100644 --- a/src/scenic/syntax/veneer.py +++ b/src/scenic/syntax/veneer.py @@ -1439,15 +1439,15 @@ def On(thing): if isA(thing, Object): # Target is an Object: use its onSurface. target = thing.onSurface + elif canCoerce(thing, Vector, exact=True): + # Target is a vector + target = toVector(thing) elif canCoerce(thing, Region): # Target is a region (or could theoretically be coerced to one), # so we can use it as a target. - target = thing + target = toType(thing, Region) else: - # Target is a vector, so we can use it as a target. - target = toType( - thing, Vector, 'specifier "on R" with R not a Region, Object, or Vector' - ) + raise TypeError('specifier "on R" with R not a Region, Object, or Vector') props = {"position": 1}