Made LatticeTopoSeries expose the annotations of its inner shapes, added more instructions to scripting language
This commit is contained in:
parent
10e13dbbd0
commit
7cc7e5f55e
|
@ -22,10 +22,10 @@
|
|||
#***************************************************************************
|
||||
|
||||
from lattice2Common import *
|
||||
from lattice2Utils import getAnnotatedShapes
|
||||
import lattice2Markers as markers
|
||||
import lattice2CompoundExplorer as LCE
|
||||
from lattice2BaseFeature import assureProperty #assureProperty(self, selfobj, proptype, propname, defvalue, group, tooltip)
|
||||
import re
|
||||
import logging
|
||||
|
||||
import math
|
||||
|
@ -87,6 +87,12 @@ example = """
|
|||
# GT : ... t t : ... bool : pops two elements, pushes True if top > bot, False otherwise. Comparing elements of different types will always return False.
|
||||
# LE : ... t t : ... bool : pops two elements, pushes True if top <= bot, False otherwise. Comparing elements of different types will always return False.
|
||||
# GE : ... t t : ... bool : pops two elements, pushes True if top >= bot, False otherwise. Comparing elements of different types will always return False.
|
||||
# AND : ... bool bool : ... bool : pops two booleans, pushes (top and bot)
|
||||
# OR : ... bool bool : ... bool : pops two booleans, pushes (top or bot)
|
||||
# XOR : ... bool bool : ... bool : pops two booleans, pushes (top xor bot)
|
||||
# NAND : ... bool bool : ... bool : pops two booleans, pushes (top nand bot)
|
||||
# NOR : ... bool bool : ... bool : pops two booleans, pushes (top nor bot)
|
||||
# NXOR : ... bool bool : ... bool : pops two booleans, pushes (top nxor bot)
|
||||
# DROP : ... t : ... : pops the top of the stack and discards it
|
||||
# DUP : ... t : ... t t : pops the top of the stack, and pushes it back twice (duplication of the top of the stack)
|
||||
# SWAP : ... t u : ... u t : pops two elements, and pushes them back in reverse order
|
||||
|
@ -98,12 +104,13 @@ example = """
|
|||
# NOP : ... : ... : No operation (NOOP is also accepted)
|
||||
# SET xyz : ... t : ... : pops the top of the stack, and saves it as a global variable named xyz
|
||||
# GET xyz : ... : ... t : pushes the contents of the global variable xyz on top of the stack
|
||||
# .Annotation : ... geometry : ... string : gets the annotation from the given geometry element. Annotations are stored as dummy de-activated Block constraints named __theannotation.123 (where 123 is the constraint's ID). A separate macro allows setting these annotations.
|
||||
# .attribute : ... t : ... u : gets the attribute from the object on the top of the stack. Allowed attributes are {allowed_attributes}
|
||||
# .Annotations.xyz : ... annotated_shape : ... string : gets the annotation from the given geometry element. Annotations are stored as dummy de-activated Block constraints named __theannotation.123 (where 123 is the constraint's ID) and some extra annotations are supplied by some Python objects (e.g. .Annotations.Geometry for sketches). A separate macro allows setting these annotations.
|
||||
# .attribute : ... geometry : ... u : gets the attribute from the object on the top of the stack. Allowed attributes are {allowed_attributes}
|
||||
|
||||
# The initial stack contains a list of sketch geometry elements.
|
||||
|
||||
QUOTE
|
||||
.Annotations.Geometry
|
||||
.Construction
|
||||
NOT
|
||||
END QUOTE
|
||||
|
@ -111,18 +118,18 @@ FILTER
|
|||
"""
|
||||
example = example.format(allowed_attributes=",".join(sorted(["%s (%s)" % (k,v) for k,v in allowed_attributes.items()])))
|
||||
|
||||
def interpret_map(stack, env, closure, lst, edge_annotations):
|
||||
def interpret_map(stack, env, closure, lst):
|
||||
result = []
|
||||
for elt in lst:
|
||||
stack, env = interpret(stack + closure[1] + [elt], env, closure[0], edge_annotations)
|
||||
stack, env = interpret(stack + closure[1] + [elt], env, closure[0])
|
||||
result.append(stack[-1])
|
||||
stack = stack[:-1]
|
||||
return (stack + [('list', result)], env)
|
||||
|
||||
def interpret_filter(stack, env, closure, lst, edge_annotations):
|
||||
def interpret_filter(stack, env, closure, lst):
|
||||
result = []
|
||||
for elt in lst:
|
||||
stack, env = interpret(stack + closure[1] + [elt], env, closure[0], edge_annotations)
|
||||
stack, env = interpret(stack + closure[1] + [elt], env, closure[0])
|
||||
if stack[-1][0] != 'bool':
|
||||
raise ValueError("closure passed as an argument to filter should return a bool, but it returned a " + str(stack[-1][0]))
|
||||
if stack[-1][1] == True:
|
||||
|
@ -134,30 +141,44 @@ def interpret_filter(stack, env, closure, lst, edge_annotations):
|
|||
stack = stack[:-1]
|
||||
return (stack + [('list', result)], env)
|
||||
|
||||
def interpret(stack, env, program, edge_annotations):
|
||||
quoting = False
|
||||
boolOperations = {
|
||||
'AND': (lambda x, y: x and y),
|
||||
'OR': (lambda x, y: x or y),
|
||||
'XOR': (lambda x, y: (x or y) and (not (x and y))),
|
||||
'NAND': (lambda x, y: not (x and y)),
|
||||
'NOR': (lambda x, y: not (x or y)),
|
||||
'NXOR': (lambda x, y: (x and y) or (not (x or y)))
|
||||
}
|
||||
|
||||
def interpret(stack, env, program):
|
||||
quoting = 0
|
||||
for line in program:
|
||||
if line.strip() == '':
|
||||
pass # no-op
|
||||
|
||||
# quotations
|
||||
elif quoting and line == 'END QUOTE':
|
||||
quoting = False
|
||||
elif quoting and line == 'QUOTE':
|
||||
raise ValueError("nested quotes are not allowed, use PARTIAL instead")
|
||||
elif quoting:
|
||||
elif quoting > 0:
|
||||
if stack[-1][0] != 'closure':
|
||||
raise ValueError("while in quote mode the top of the stack should be a closure")
|
||||
if line == 'END QUOTE':
|
||||
quoting = quoting - 1
|
||||
elif line == 'QUOTE':
|
||||
quoting = quoting + 1
|
||||
|
||||
if quoting > 0: # if this wasn't the last 'END QUOTE', i.e. after decrementing we're still quoting, then add the instruction to the closure
|
||||
stack[-1] = ('closure', (stack[-1][1][0] + [line], stack[-1][1][1]))
|
||||
else:
|
||||
pass
|
||||
|
||||
elif line == 'QUOTE':
|
||||
stack.append(('closure', ([],[])))
|
||||
quoting = True
|
||||
quoting = 1
|
||||
|
||||
# functions
|
||||
elif line == 'CALL':
|
||||
if stack[-1][0] != "closure":
|
||||
raise ValueError("CALL expected a closure")
|
||||
stack, env = interpret(stack[:-1] + stack[-1][1][1], env, stack[-1][1][0], edge_annotations)
|
||||
stack, env = interpret(stack[:-1] + stack[-1][1][1], env, stack[-1][1][0])
|
||||
elif line == 'PARTIAL':
|
||||
# push stack[-2] onto the closure's stack
|
||||
if stack[-1][0] != "closure":
|
||||
|
@ -169,14 +190,14 @@ def interpret(stack, env, program, edge_annotations):
|
|||
if stack[-2][0] != "list":
|
||||
raise ValueError("MAP expected a list as the second (deeper) element on the stack")
|
||||
closure = stack[-1][1]
|
||||
stack, env = interpret_map(stack[:-2], env, stack[-1][1], stack[-2][1], edge_annotations)
|
||||
stack, env = interpret_map(stack[:-2], env, stack[-1][1], stack[-2][1])
|
||||
elif line == 'FILTER':
|
||||
if stack[-1][0] != "closure":
|
||||
raise ValueError("FILTER expected a closure at the top of the stack")
|
||||
if stack[-2][0] != "list":
|
||||
raise ValueError("FILTER expected a list as the second (deeper) element on the stack")
|
||||
closure = stack[-1][1]
|
||||
stack, env = interpret_filter(stack[:-2], env, stack[-1][1], stack[-2][1], edge_annotations)
|
||||
stack, env = interpret_filter(stack[:-2], env, stack[-1][1], stack[-2][1])
|
||||
|
||||
elif line == 'DEBUG':
|
||||
print('stack:')
|
||||
|
@ -207,6 +228,12 @@ def interpret(stack, env, program, edge_annotations):
|
|||
stack = stack[:-2] + [("bool", stack[-1] <= stack[-2])]
|
||||
elif line == 'GE':
|
||||
stack = stack[:-2] + [("bool", stack[-1] >= stack[-2])]
|
||||
elif line in boolOperations:
|
||||
if stack[-1][0] != "bool":
|
||||
raise ValueError(line + " expected a boolean at the top of the stack")
|
||||
if stack[-2][0] != "bool":
|
||||
raise ValueError(line + " expected a boolean as the second (deeper) element on the stack")
|
||||
stack = stack[:-2] + [("bool", boolOperations[line](stack[-1], stack[-2]))]
|
||||
elif line == 'DROP':
|
||||
stack = stack[:-1]
|
||||
elif line == 'DUP':
|
||||
|
@ -236,33 +263,32 @@ def interpret(stack, env, program, edge_annotations):
|
|||
stack = stack[:-1]
|
||||
elif line.startswith('GET '):
|
||||
stack.append(env[line[len('GET '):]])
|
||||
elif line == '.Annotation':
|
||||
if stack[-1][0] != "geometry":
|
||||
raise ValueError(".Annotation expected a geometry elemnt")
|
||||
stack = stack[:-1] + [('string', edge_annotations.get(stack[-1][1][0], None))]
|
||||
elif line.startswith('.Annotations.'):
|
||||
annotationName = line[len('.Annotations.'):]
|
||||
if stack[-1][0] != "annotated_shape":
|
||||
raise ValueError(".Annotations expected an annotated shape")
|
||||
stack = stack[:-1] + [stack[-1][1][1].get(annotationName, ('none', None))]
|
||||
elif line in allowed_attributes.keys():
|
||||
if stack[-1][0] != "geometry":
|
||||
raise ValueError(line + " expected a geometry elemnt")
|
||||
stack = stack[:-1] + [(allowed_attributes[line], getattr(stack[-1][1][1], line[1:]))]
|
||||
else:
|
||||
raise ValueError('Unknown operation: "' + line + '"' + repr(line) + ". " + str(line.strip() == '') + "; " + str(len(line)))
|
||||
raise ValueError('Unknown operation: "' + line + '", repr=' + repr(line) + ". empty after strip=" + str(line.strip() == '') + ", len=" + str(len(line)))
|
||||
return stack, env
|
||||
|
||||
def user_filter(filter, sketch):
|
||||
filter = [line.lstrip() for line in filter]
|
||||
filter = [line for line in filter if not line.startswith('#')]
|
||||
constraint_re_matches = [(c,re.match(r"^__(.*)\.[0-9]+$", c.Name)) for c in sketch.Constraints]
|
||||
edge_annotations = dict((c.First,match.group(1)) for c,match in constraint_re_matches if match)
|
||||
stack = [('list', [('geometry', (i, g)) for i, g in enumerate(sketch.Geometry)])]
|
||||
stack, env = interpret(stack, {}, filter, edge_annotations)
|
||||
stack = [('list', [('annotated_shape', sh) for sh in getAnnotatedShapes(sketch)])]
|
||||
stack, env = interpret(stack, {}, filter)
|
||||
if len(stack) != 1:
|
||||
raise ValueError("The stack should contain a single element after applying the filter's operations.")
|
||||
if stack[0][0] != 'list':
|
||||
raise ValueError("The stack should contain a list after applying the filter's operations.")
|
||||
for i, (type, g) in enumerate(stack[0][1]):
|
||||
if type != 'geometry':
|
||||
raise ValueError("The stack should contain a list of geometry elemnents after applying the filter's operations, wrong type for list element " + str(i) + " : " + str(type))
|
||||
return [g for type,(id,g) in stack[0][1]]
|
||||
if type != 'annotated_shape':
|
||||
raise ValueError("The stack should contain a list of annotated shape elemnents after applying the filter's operations, wrong type for list element " + str(i) + " : " + str(type))
|
||||
return [shape for type,(shape,annotations) in stack[0][1]]
|
||||
|
||||
|
||||
class _latticeDowngrade:
|
||||
|
@ -318,7 +344,6 @@ class _latticeDowngrade:
|
|||
rst = shp.Edges
|
||||
elif obj.Mode == 'SketchEdges':
|
||||
rst = user_filter(obj.Filter, obj.Base)
|
||||
rst = [g.toShape() for g in rst]
|
||||
elif obj.Mode == 'Seam edges':
|
||||
rst = getAllSeams(shp)
|
||||
elif obj.Mode == 'Non-seam edges':
|
||||
|
|
|
@ -32,10 +32,12 @@ import FreeCAD as App
|
|||
import Part
|
||||
|
||||
from lattice2Common import *
|
||||
from lattice2Utils import getAnnotatedShapes
|
||||
import lattice2BaseFeature
|
||||
import lattice2Executer as Executer
|
||||
import lattice2Markers as markers
|
||||
import lattice2Subsequencer as Subsequencer
|
||||
from lattice2BaseFeature import assureProperty #assureProperty(self, selfobj, proptype, propname, defvalue, group, tooltip)
|
||||
|
||||
# --------------------------- general routines ------------------------------------------------
|
||||
|
||||
|
@ -93,6 +95,13 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
obj.Recomputing = ["Disabled", "Recompute Once", "Enabled"]
|
||||
obj.Recomputing = "Disabled" # recomputing TopoSeries can be very long, so disable it by default
|
||||
|
||||
self.assureProperties(obj)
|
||||
|
||||
def assureProperties(self, selfobj):
|
||||
# We cannot use a hidden PythonObject property, because it contains Edges and they are not serializable
|
||||
#assureProperty(selfobj, "App::PropertyPythonObject", "AnnotatedShapes", [], "Lattice TopoSeries", "internal: stores the list of shapes with their annotations")
|
||||
self.AnnotatedShapes = []
|
||||
|
||||
def makeSubsequence(self, selfobj, object_to_loop):
|
||||
|
||||
# gather up the links
|
||||
|
@ -128,6 +137,8 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
if selfobj.Recomputing == "Disabled":
|
||||
raise ValueError(selfobj.Name+": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it.")
|
||||
try:
|
||||
allAnnotatedShapes = []
|
||||
self.assureProperties(selfobj)
|
||||
|
||||
# do the subsequencing in this document first, to verify stuff is set up correctly, and to obtain sequence length
|
||||
if self.isVerbose():
|
||||
|
@ -184,7 +195,7 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
doc2.recompute()
|
||||
|
||||
#get shape
|
||||
shape = None
|
||||
annotatedShapes = None
|
||||
for obj in doc2.Objects:
|
||||
if 'Invalid' in obj.State:
|
||||
Executer.error(obj,"Recomputing shape for subsequence index "+repr(i)+" failed.")
|
||||
|
@ -197,10 +208,11 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
pass
|
||||
if scale < DistConfusion * 100:
|
||||
scale = 1.0
|
||||
shape = markers.getNullShapeShape(scale)
|
||||
if shape is None:
|
||||
shape = object_to_take_in_doc2.Shape.copy()
|
||||
output_shapes.append(shape)
|
||||
annotatedShapes = [(markers.getNullShapeShape(scale), { 'IsNullShape': ('boolean', True) })]
|
||||
if annotatedShapes is None:
|
||||
annotatedShapes = getAnnotatedShapes(object_to_take_in_doc2)
|
||||
output_shapes.append(Part.Compound([sh for sh, ann in annotatedShapes]))
|
||||
allAnnotatedShapes.extend(annotatedShapes)
|
||||
|
||||
#update progress
|
||||
if bGui:
|
||||
|
@ -223,6 +235,7 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
|
||||
|
||||
selfobj.Shape = Part.makeCompound(output_shapes)
|
||||
self.AnnotatedShapes = allAnnotatedShapes
|
||||
|
||||
output_is_lattice = lattice2BaseFeature.isObjectLattice(screen(selfobj.ObjectToTake))
|
||||
if 'Auto' in selfobj.isLattice:
|
||||
|
@ -234,6 +247,12 @@ class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
|
|||
selfobj.Recomputing = "Disabled"
|
||||
return "suppress" # "suppress" disables most convenience code of lattice2BaseFeature. We do it because we build a nested array, which are not yet supported by lattice WB.
|
||||
|
||||
def getAnnotatedShapes(self, selfobj):
|
||||
# The attribute is not serializable (because it contains Edges), so it is necessary to execute() the object at least once to get the AnnotatedShapes
|
||||
if not hasattr(self, 'AnnotatedShapes'):
|
||||
self.execute(selfobj)
|
||||
return self.AnnotatedShapes
|
||||
|
||||
class ViewProviderLatticeTopoSeries(lattice2BaseFeature.ViewProviderLatticeFeature):
|
||||
|
||||
def getIcon(self):
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
import re
|
||||
|
||||
__title__="Utility functions for Lattice2"
|
||||
__author__ = "DeepSOIC"
|
||||
__url__ = ""
|
||||
|
@ -92,3 +94,28 @@ def linkSubList_convertToOldStyle(references):
|
|||
# old style references, no conversion required
|
||||
result.append(tup)
|
||||
return result
|
||||
|
||||
def getAnnotatedShape(object, i, g, annotations_from_constraints):
|
||||
kvs = annotations_from_constraints.get(i, '')
|
||||
kvs = kvs.split(',')
|
||||
if kvs == ['']:
|
||||
kvs = []
|
||||
kvs = [kv.split('=',1) for kv in kvs]
|
||||
kvs = [(kv[0], ('string', kv[1])) if len(kv) == 2 else (kv[0], ('bool', True)) for kv in kvs]
|
||||
kvs = dict(kvs, Geometry=('geometry', g))
|
||||
sh = g.toShape().copy()
|
||||
sh.Placement = object.Placement
|
||||
return (sh, kvs)
|
||||
|
||||
def getAnnotatedShapes(object):
|
||||
""" returns a list of the form [ (shape, { 'k': ('type', v), … }) …] """
|
||||
if object.TypeId == 'Sketcher::SketchObject':
|
||||
constraint_re_matches = [(c,re.match(r"^__(.*)\.[0-9]+$", c.Name)) for c in object.Constraints]
|
||||
annotations_from_constraints = dict((c.First,match.group(1)) for c,match in constraint_re_matches if match)
|
||||
return [getAnnotatedShape(object, i, g, annotations_from_constraints) for i,g in enumerate(object.Geometry)]
|
||||
elif hasattr(object, 'getAnnotatedShapes') and callable(object.getAnnotatedShapes):
|
||||
return object.getAnnotatedShapes()
|
||||
elif hasattr(object, 'Proxy') and hasattr(object.Proxy, 'getAnnotatedShapes') and callable(object.Proxy.getAnnotatedShapes):
|
||||
return object.Proxy.getAnnotatedShapes(object)
|
||||
else:
|
||||
return [(object.Shape.copy(), dict())]
|
||||
|
|
Loading…
Reference in New Issue
Block a user