Change logical ops names, add tests, general code cleanup
Changed the op names to and or not exc(ept) Added more test-cases Cleanup up the Selectors code Code should be ready for merging now
This commit is contained in:
@ -289,10 +289,6 @@ class DirectionMinMaxSelector(Selector):
|
||||
|
||||
CQ(aCube).faces( ">Z" )
|
||||
|
||||
Future Enhancements:
|
||||
provide a nicer way to select in arbitrary directions. IE, a bit more code could
|
||||
allow '>(0,0,1)' to work.
|
||||
|
||||
"""
|
||||
def __init__(self, vector, directionMax=True, tolerance=0.0001):
|
||||
self.vector = vector
|
||||
@ -424,7 +420,7 @@ class InverseSelector(Selector):
|
||||
|
||||
def _makeGrammar():
|
||||
"""
|
||||
Define the string selector grammar using PyParsing
|
||||
Define the simple string selector grammar using PyParsing
|
||||
"""
|
||||
|
||||
#float definition
|
||||
@ -479,42 +475,12 @@ _grammar = _makeGrammar() #make a grammar instance
|
||||
|
||||
class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
|
||||
are also available ( usually with more functionality ) through the creation of full-fledged
|
||||
selector objects. see :py:class:`Selector` and its subclasses
|
||||
|
||||
Filtering works differently depending on the type of object list being filtered.
|
||||
|
||||
:param selectorString: A two-part selector string, [selector][axis]
|
||||
|
||||
:return: objects that match the specified selector
|
||||
|
||||
***Modfiers*** are ``('|','+','-','<','>','%')``
|
||||
|
||||
:\|:
|
||||
parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
|
||||
:#:
|
||||
perpendicular to (same as :py:class:`PerpendicularDirSelector` )
|
||||
:+:
|
||||
positive direction (same as :py:class:`DirectionSelector` )
|
||||
:-:
|
||||
negative direction (same as :py:class:`DirectionSelector` )
|
||||
:>:
|
||||
maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
|
||||
:<:
|
||||
minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
|
||||
:%:
|
||||
curve/surface type (same as :py:class:`TypeSelector`)
|
||||
|
||||
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ``
|
||||
|
||||
Selectors are a complex topic: see :ref:`selector_reference` for more information
|
||||
|
||||
|
||||
|
||||
This is a private class that converts a parseResults object into a simple
|
||||
selector object
|
||||
"""
|
||||
def __init__(self,selectorString):
|
||||
def __init__(self,parseResults):
|
||||
|
||||
#define all token to object mappings
|
||||
self.axes = {
|
||||
'X': Vector(1,0,0),
|
||||
'Y': Vector(0,1,0),
|
||||
@ -546,11 +512,8 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
'#' : PerpendicularDirSelector,
|
||||
'|' : ParallelDirSelector}
|
||||
|
||||
|
||||
self.selectorString = selectorString
|
||||
#this class is going to be refactored - it will accept ParseResults objects as input i.s.o. strings
|
||||
parsing_result = selectorString#_grammar.parseString(selectorString,parseAll=True)
|
||||
self.mySelector = self._chooseSelector(parsing_result)
|
||||
self.parseResults = parseResults
|
||||
self.mySelector = self._chooseSelector(parseResults)
|
||||
|
||||
def _chooseSelector(self,pr):
|
||||
"""
|
||||
@ -597,63 +560,45 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
return self.mySelector.filter(objectList)
|
||||
|
||||
|
||||
def _makeSelector(expr):
|
||||
"""
|
||||
|
||||
"""
|
||||
return _SimpleStringSyntaxSelector(expr)
|
||||
|
||||
def _makeInverseSelector(expr):
|
||||
"""
|
||||
|
||||
"""
|
||||
if 'NOT' in expr:
|
||||
return InverseSelector(expr[1])
|
||||
else:
|
||||
return expr
|
||||
|
||||
def _makeExpressionGrammar(atom):
|
||||
"""
|
||||
|
||||
Define the complex string selector grammar using PyParsing (which supports
|
||||
logical operations and nesting)
|
||||
"""
|
||||
and_op = Literal('&').setResultsName('AND')
|
||||
or_op = Literal('|').setResultsName('OR')
|
||||
delta_op = Literal('^').setResultsName('DELTA')
|
||||
not_op = Literal('~').setResultsName('NOT')
|
||||
|
||||
binary_op = and_op | or_op | delta_op
|
||||
#define operators
|
||||
and_op = Literal('and')
|
||||
or_op = Literal('or')
|
||||
delta_op = Literal('exc') | Literal('except')
|
||||
not_op = Literal('not')
|
||||
|
||||
atom.setParseAction(_makeSelector)
|
||||
atom.setResultsName('atom')
|
||||
def atom_callback(res):
|
||||
return _SimpleStringSyntaxSelector(res)
|
||||
|
||||
atom_ = atom | not_op + atom
|
||||
atom_.setParseAction(_makeInverseSelector)
|
||||
atom.setParseAction(atom_callback) #construct a simple selector from every matched
|
||||
|
||||
#infix notation
|
||||
#define callback functions for all operations
|
||||
def and_callback(res):
|
||||
items = res.asList()[0][::2]
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(AndSelector,items)
|
||||
|
||||
def or_callback(res):
|
||||
items = res.asList()[0][::2]
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(SumSelector,items)
|
||||
|
||||
def delta_callback(res):
|
||||
items = res.asList()[0][::2]
|
||||
def exc_callback(res):
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(SubtractSelector,items)
|
||||
|
||||
def not_callback(res):
|
||||
right = res.asList()[0][1]
|
||||
right = res.asList()[0][1] #take second item, i.e. the operand
|
||||
return InverseSelector(right)
|
||||
|
||||
#this version does not support nesting
|
||||
#expr = atom_ + ZeroOrMore(binary_op + atom_)
|
||||
#this version does support nesting: i.e. A & (B | C)
|
||||
expr = infixNotation(atom_,
|
||||
#construct the final grammar and set all the callbacks
|
||||
expr = infixNotation(atom,
|
||||
[(and_op,2,opAssoc.LEFT,and_callback),
|
||||
(or_op,2,opAssoc.LEFT,or_callback),
|
||||
(delta_op,2,opAssoc.LEFT,delta_callback),
|
||||
(delta_op,2,opAssoc.LEFT,exc_callback),
|
||||
(not_op,1,opAssoc.RIGHT,not_callback)])
|
||||
|
||||
return expr
|
||||
@ -662,11 +607,57 @@ _expression_grammar = _makeExpressionGrammar(_grammar)
|
||||
|
||||
class StringSyntaxSelector(Selector):
|
||||
"""
|
||||
Implment StringExpresionSelector here
|
||||
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
|
||||
are also available ( usually with more functionality ) through the creation of full-fledged
|
||||
selector objects. see :py:class:`Selector` and its subclasses
|
||||
|
||||
Filtering works differently depending on the type of object list being filtered.
|
||||
|
||||
:param selectorString: A two-part selector string, [selector][axis]
|
||||
|
||||
:return: objects that match the specified selector
|
||||
|
||||
***Modfiers*** are ``('|','+','-','<','>','%')``
|
||||
|
||||
:\|:
|
||||
parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
|
||||
:#:
|
||||
perpendicular to (same as :py:class:`PerpendicularDirSelector` )
|
||||
:+:
|
||||
positive direction (same as :py:class:`DirectionSelector` )
|
||||
:-:
|
||||
negative direction (same as :py:class:`DirectionSelector` )
|
||||
:>:
|
||||
maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
|
||||
:<:
|
||||
minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
|
||||
:%:
|
||||
curve/surface type (same as :py:class:`TypeSelector`)
|
||||
|
||||
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ`` or ``(x,y,z)`` which defines an arbitrary direction
|
||||
|
||||
It is possible to combine simple selectors together using logical operations.
|
||||
The following operations are suuported
|
||||
|
||||
:and:
|
||||
Logical AND, e.g. >X and >Y
|
||||
:or:
|
||||
Logical OR, e.g. |X or |Y
|
||||
:not:
|
||||
Logical NOT, e.g. not #XY
|
||||
:exc(ept):
|
||||
Set difference (equivalent to AND NOT): |X exc >Z
|
||||
|
||||
Finally, it is also possible to use even more complex expressions with nesting
|
||||
and arbitrary number of terms, e.g.
|
||||
|
||||
(not >X[0] and #XY) or >XY[0]
|
||||
|
||||
Selectors are a complex topic: see :ref:`selector_reference` for more information
|
||||
"""
|
||||
def __init__(self,selectorString):
|
||||
"""
|
||||
|
||||
Feed the input string through the parser and construct an relevant complex selector object
|
||||
"""
|
||||
self.selectorString = selectorString
|
||||
parse_result = _expression_grammar.parseString(selectorString,
|
||||
@ -675,14 +666,6 @@ class StringSyntaxSelector(Selector):
|
||||
|
||||
def filter(self,objectList):
|
||||
"""
|
||||
|
||||
Filter give object list through th already constructed complex selector object
|
||||
"""
|
||||
return self.mySelector.filter(objectList)
|
||||
|
||||
if __name__ == '__main__':
|
||||
#this code is meant for testing - to be removed!
|
||||
|
||||
#parse a simple expression
|
||||
rs = StringSyntaxSelector('~|(1,1,0) & >(0,0,1) | XY & >(1,1,1)[-1]')
|
||||
#parse a nested expression
|
||||
rn = StringSyntaxSelector('(~|(1,1,0) & >(0,0,1)) | XY & (Z | X)')
|
||||
@ -339,6 +339,10 @@ class TestCQSelectors(BaseTest):
|
||||
el = c.edges(S('|X') & BS((-2,-2,0.1), (2,2,2))).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
v = c.vertices(">X and >Y").vals()
|
||||
self.assertEqual(2, len(v))
|
||||
|
||||
def testSumSelector(self):
|
||||
c = CQ(makeUnitCube())
|
||||
|
||||
@ -356,15 +360,11 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertEqual(8, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
fl = c.faces(">Z | <Z").vals()
|
||||
fl = c.faces(">Z or <Z").vals()
|
||||
self.assertEqual(2, len(fl))
|
||||
el = c.edges("|X | |Y").vals()
|
||||
el = c.edges("|X or |Y").vals()
|
||||
self.assertEqual(8, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
v = c.vertices(">X & >Y").vals()
|
||||
self.assertEqual(2, len(v))
|
||||
|
||||
def testSubtractSelector(self):
|
||||
c = CQ(makeUnitCube())
|
||||
|
||||
@ -378,7 +378,7 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertEqual(3, len(fl))
|
||||
|
||||
# test using extended string syntax
|
||||
fl = c.faces("#Z ^ >X").vals()
|
||||
fl = c.faces("#Z exc >X").vals()
|
||||
self.assertEqual(3, len(fl))
|
||||
|
||||
def testInverseSelector(self):
|
||||
@ -398,11 +398,18 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
fl = c.faces('~>Z').vals()
|
||||
fl = c.faces('not >Z').vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges('~>X').vals()
|
||||
el = c.faces('>Z').edges('not >X').vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
def testComplexStringSelector(self):
|
||||
c = CQ(makeUnitCube())
|
||||
|
||||
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
|
||||
self.assertEqual(4, len(v))
|
||||
|
||||
|
||||
def testFaceCount(self):
|
||||
c = CQ(makeUnitCube())
|
||||
self.assertEqual( 6, c.faces().size() )
|
||||
@ -426,7 +433,7 @@ class TestCQSelectors(BaseTest):
|
||||
Test if reasonable string selector expressions parse without an error
|
||||
"""
|
||||
|
||||
gram = selectors._makeGrammar()
|
||||
gram = selectors._expression_grammar
|
||||
|
||||
expressions = ['+X ',
|
||||
'-Y',
|
||||
@ -443,7 +450,9 @@ class TestCQSelectors(BaseTest):
|
||||
'left',
|
||||
'right',
|
||||
'top',
|
||||
'bottom']
|
||||
'bottom',
|
||||
'not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]',
|
||||
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)']
|
||||
|
||||
for e in expressions: gram.parseString(e)
|
||||
|
||||
Reference in New Issue
Block a user