def Facing(heading): if isinstance(heading, VectorField): return Specifier('heading', DelayedArgument({'position'}, lambda self: heading[self.position])) else: heading = toHeading(heading, 'specifier "facing X" with X not a heading or vector field') return Specifier('heading', heading)
def Facing(heading): """The 'facing X' polymorphic specifier. Specifies 'heading', with dependencies depending on the form: facing <number> -- no dependencies; facing <field> -- depends on 'position'. """ if isinstance(heading, VectorField): return Specifier('heading', DelayedArgument({'position'}, lambda self: heading[self.position])) else: heading = toHeading(heading, 'specifier "facing X" with X not a heading or vector field') return Specifier('heading', heading)
def ApparentlyFacing(heading, fromPt=None): heading = toHeading(heading, 'specifier "apparently facing X" with X not a heading') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "apparently facing X from Y" with Y not a vector') value = lambda self: fromPt.angleTo(self.position) + heading return Specifier('heading', DelayedArgument({'position'}, value))
def OffsetBy(offset): """The 'offset by <vector>' specifier. Specifies 'position', with no dependencies. """ offset = toVector(offset, 'specifier "offset by X" with X not a vector') pos = RelativeTo(offset, ego()).toVector() return Specifier('position', pos)
def FacingToward(pos): """The 'facing toward <vector>' specifier. Specifies 'heading', depending on 'position'. """ pos = toVector(pos, 'specifier "facing toward X" with X not a vector') return Specifier('heading', DelayedArgument({'position'}, lambda self: self.position.angleTo(pos)))
def In(region): # TODO fix type checking #region = toType(region, Region, 'specifier "in/on R" with R not a Region') if isinstance(region, Distribution): extras = {'heading'} # TODO fix hack!!! else: extras = {} if region.orientation is None else {'heading'} return Specifier('position', Region.uniformPointIn(region), optionals=extras)
def In(region): """The 'in/on <region>' specifier. Specifies 'position', with no dependencies. Optionally specifies 'heading' if the given Region has a preferred orientation. """ region = toType(region, Region, 'specifier "in/on R" with R not a Region') extras = {'heading'} if alwaysProvidesOrientation(region) else {} return Specifier('position', Region.uniformPointIn(region), optionals=extras)
def OffsetAlongSpec(direction, offset): """The 'offset along X by Y' polymorphic specifier. Specifies 'position', with no dependencies. Allowed forms: offset along <heading> by <vector> offset along <field> by <vector> """ return Specifier('position', OffsetAlong(ego(), direction, offset))
def VisibleFrom(base): """The 'visible from <Point>' specifier. Specifies 'position', with no dependencies. This uses the given object's 'visibleRegion' property, and so correctly handles the view regions of Points, OrientedPoints, and Objects. """ if not isinstance(base, Point): raise RuntimeParseError('specifier "visible from O" with O not a Point') return Specifier('position', Region.uniformPointIn(base.visibleRegion))
def Beyond(pos, offset, fromPt=None): pos = toVector(pos, 'specifier "beyond X by Y" with X not a vector') dType = underlyingType(offset) if dType is float or dType is int: offset = Vector(0, offset) elif dType is not Vector: raise RuntimeParseError('specifier "beyond X by Y" with Y not a number or vector') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "beyond X by Y from Z" with Z not a vector') lineOfSight = fromPt.angleTo(pos) return Specifier('position', pos.offsetRotated(lineOfSight, offset))
def ApparentlyFacing(heading, fromPt=None): """The 'apparently facing <heading> [from <vector>]' specifier. Specifies 'heading', depending on 'position'. If the 'from <vector>' is omitted, the position of ego is used. """ heading = toHeading(heading, 'specifier "apparently facing X" with X not a heading') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "apparently facing X from Y" with Y not a vector') value = lambda self: fromPt.angleTo(self.position) + heading return Specifier('heading', DelayedArgument({'position'}, value))
def leftSpecHelper(syntax, pos, dist, axis, toComponents, makeOffset): extras = set() if canCoerce(dist, float): dx, dy = toComponents(coerce(dist, float)) elif canCoerce(dist, Vector): dx, dy = coerce(dist, Vector) else: raise RuntimeParseError(f'"{syntax} X by D" with D not a number or vector') if isinstance(pos, OrientedPoint): # TODO too strict? val = lambda self: pos.relativize(makeOffset(self, dx, dy)) new = DelayedArgument({axis}, val) extras.add('heading') else: pos = toVector(pos, f'specifier "{syntax} X" with X not a vector') val = lambda self: pos.offsetRotated(self.heading, makeOffset(self, dx, dy)) new = DelayedArgument({axis, 'heading'}, val) return Specifier('position', new, optionals=extras)
def Beyond(pos, offset, fromPt=None): """The 'beyond X by Y [from Z]' polymorphic specifier. Specifies 'position', with no dependencies. Allowed forms: beyond <vector> by <number> [from <vector>] beyond <vector> by <vector> [from <vector>] If the 'from <vector>' is omitted, the position of ego is used. """ pos = toVector(pos, 'specifier "beyond X by Y" with X not a vector') dType = underlyingType(offset) if dType is float or dType is int: offset = Vector(0, offset) elif dType is not Vector: raise RuntimeParseError('specifier "beyond X by Y" with Y not a number or vector') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "beyond X by Y from Z" with Z not a vector') lineOfSight = fromPt.angleTo(pos) return Specifier('position', pos.offsetRotated(lineOfSight, offset))
def Following(field, dist, fromPt=None): """The 'following F [from X] for D' specifier. Specifies 'position', and optionally 'heading', with no dependencies. Allowed forms: following <field> [from <vector>] for <number> If the 'from <vector>' is omitted, the position of ego is used. """ if fromPt is None: fromPt = ego() else: dist, fromPt = fromPt, dist if not isinstance(field, VectorField): raise RuntimeParseError('"following F" specifier with F not a vector field') fromPt = toVector(fromPt, '"following F from X for D" with X not a vector') dist = toScalar(dist, '"following F for D" with D not a number') pos = field.followFrom(fromPt, dist) heading = field[pos] val = OrientedVector.make(pos, heading) return Specifier('position', val, optionals={'heading'})
def At(pos): """The 'at <vector>' specifier. Specifies 'position', with no dependencies.""" pos = toVector(pos, 'specifier "at X" with X not a vector') return Specifier('position', pos)
def With(prop, val): """The 'with <property> <value>' specifier. Specifies the given property, with no dependencies. """ return Specifier(prop, val)
def OffsetAlongSpec(direction, offset): return Specifier('position', OffsetAlong(ego(), direction, offset))
def OffsetBy(offset): offset = toVector(offset, 'specifier "offset by X" with X not a vector') pos = RelativeTo(offset, ego()).toVector() return Specifier('position', pos)
def __init__(self, *args, **kwargs): # Validate specifiers name = type(self).__name__ specifiers = list(args) for prop, val in kwargs.items(): specifiers.append(Specifier(prop, val)) properties = dict() optionals = collections.defaultdict(list) defs = self.defaults() for spec in specifiers: if not isinstance(spec, Specifier): raise RuntimeParseError( f'argument {spec} to {name} is not a specifier') prop = spec.property if prop in properties: raise RuntimeParseError( f'property "{prop}" of {name} specified twice') properties[prop] = spec for opt in spec.optionals: if opt in defs: # do not apply optionals for properties this object lacks optionals[opt].append(spec) # Decide which optionals to use optionalsForSpec = collections.defaultdict(set) for opt, specs in optionals.items(): if opt in properties: continue # optionals do not override a primary specification if len(specs) > 1: raise RuntimeParseError( f'property "{opt}" of {name} specified twice (optionally)') assert len(specs) == 1 spec = specs[0] properties[opt] = spec optionalsForSpec[spec].add(opt) # Add any default specifiers needed for prop in defs: if prop not in properties: spec = defs[prop] specifiers.append(spec) properties[prop] = spec # Topologically sort specifiers order = [] seen, done = set(), set() def dfs(spec): if spec in done: return elif spec in seen: raise RuntimeParseError( f'specifier for property {spec.property} ' 'depends on itself') seen.add(spec) for dep in spec.requiredProperties: child = properties.get(dep) if child is None: raise RuntimeParseError( f'property {dep} required by ' f'specifier {spec} is not specified') else: dfs(child) order.append(spec) done.add(spec) for spec in specifiers: dfs(spec) assert len(order) == len(specifiers) # Evaluate and apply specifiers for spec in order: spec.applyTo(self, optionalsForSpec[spec]) # Set up dependencies deps = [] for prop in properties: assert hasattr(self, prop) val = getattr(self, prop) if needsSampling(val): deps.append(val) super().__init__(deps) self.properties = set(properties)
def withProperties(cls, props): assert all(reqProp in props for reqProp in cls.defaults()) assert all(not needsLazyEvaluation(val) for val in props.values()) specs = (Specifier(prop, val) for prop, val in props.items()) return cls(*specs)
def At(pos): pos = toVector(pos, 'specifier "at X" with X not a vector') return Specifier('position', pos)
def VisibleFrom(base): if not isinstance(base, Point): raise RuntimeParseError('specifier "visible from O" with O not a Point') return Specifier('position', Region.uniformPointIn(base.visibleRegion))
def test_pickle_object(): spec = Specifier( 'blob', DelayedArgument(('width', ), lambda context: 2 * context.width), {'width'}) obj = Object(spec) tryPickling(obj)
def With(prop, val): return Specifier(prop, val)
def __init__(self, *args, _internal=False, **kwargs): if _internal: # Object is being constructed internally; use fast path assert not args for prop, value in kwargs.items(): assert not needsLazyEvaluation(value), (prop, value) object.__setattr__(self, prop, value) super().__init__(kwargs.values()) self.properties = set(kwargs.keys()) return # Validate specifiers name = self.__class__.__name__ specifiers = list(args) for prop, val in kwargs.items(): # kwargs supported for internal use specifiers.append(Specifier(prop, val, internal=True)) properties = dict() optionals = collections.defaultdict(list) defs = self.__class__._defaults for spec in specifiers: assert isinstance(spec, Specifier), (name, spec) prop = spec.property if prop in properties: raise RuntimeParseError( f'property "{prop}" of {name} specified twice') properties[prop] = spec for opt in spec.optionals: if opt in defs: # do not apply optionals for properties this object lacks optionals[opt].append(spec) # Decide which optionals to use optionalsForSpec = collections.defaultdict(set) for opt, specs in optionals.items(): if opt in properties: continue # optionals do not override a primary specification if len(specs) > 1: raise RuntimeParseError( f'property "{opt}" of {name} specified twice (optionally)') assert len(specs) == 1 spec = specs[0] properties[opt] = spec optionalsForSpec[spec].add(opt) # Add any default specifiers needed for prop in defs: if prop not in properties: spec = defs[prop] specifiers.append(spec) properties[prop] = spec # Topologically sort specifiers order = [] seen, done = set(), set() def dfs(spec): if spec in done: return elif spec in seen: raise RuntimeParseError( f'specifier for property {spec.property} ' 'depends on itself') seen.add(spec) for dep in spec.requiredProperties: child = properties.get(dep) if child is None: raise RuntimeParseError( f'property {dep} required by ' f'specifier {spec} is not specified') else: dfs(child) order.append(spec) done.add(spec) for spec in specifiers: dfs(spec) assert len(order) == len(specifiers) # Evaluate and apply specifiers self.properties = set() # will be filled by calls to _specify below self._evaluated = DefaultIdentityDict( ) # temporary cache for lazily-evaluated values for spec in order: spec.applyTo(self, optionalsForSpec[spec]) del self._evaluated # Set up dependencies deps = [] for prop in properties: assert hasattr(self, prop) val = getattr(self, prop) deps.append(val) super().__init__(deps) # Possibly register this object self._register()
def FacingToward(pos): pos = toVector(pos, 'specifier "facing toward X" with X not a vector') return Specifier('heading', DelayedArgument({'position'}, lambda self: self.position.angleTo(pos)))
def withProperties(cls, props): assert all(reqProp in props for reqProp in cls.defaults()) specs = (Specifier(prop, val) for prop, val in props.items()) return cls(*specs)