diff --git a/src/Mod/Draft/draftobjects/array.py b/src/Mod/Draft/draftobjects/array.py index 946d474804fb..d9659e0f20fd 100644 --- a/src/Mod/Draft/draftobjects/array.py +++ b/src/Mod/Draft/draftobjects/array.py @@ -34,6 +34,8 @@ ## \addtogroup draftobjects # @{ import math +import json +import re from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App @@ -112,6 +114,15 @@ def set_general_properties(self, obj): _tip) obj.Fuse = False + if "Skip" not in properties: + _tip = QT_TRANSLATE_NOOP("App::Property", + "List of items in pattern to skip") + obj.addProperty("App::PropertyString", + "Skip", + "Objects", + _tip) + obj.Skip = "[]" + def set_ortho_properties(self, obj): """Set orthogonal properties only if they don't exist.""" properties = obj.PropertiesList @@ -399,6 +410,8 @@ def execute(self, obj): "reference.") raise TypeError(_info) + skip_list = parse_skip_list(obj.Skip) + if obj.ArrayType == "ortho": pls = rect_placements(obj.Base.Placement, obj.IntervalX, @@ -406,18 +419,20 @@ def execute(self, obj): obj.IntervalZ, obj.NumberX, obj.NumberY, - obj.NumberZ) + obj.NumberZ, + skip_list) elif obj.ArrayType == "polar": av = obj.IntervalAxis if hasattr(obj, "IntervalAxis") else None pls = polar_placements(obj.Base.Placement, center, obj.Angle.Value, - obj.NumberPolar, axis, av) + obj.NumberPolar, axis, av, skip_list) elif obj.ArrayType == "circular": pls = circ_placements(obj.Base.Placement, obj.RadialDistance, obj.TangentialDistance, axis, center, - obj.NumberCircles, obj.Symmetry) + obj.NumberCircles, obj.Symmetry, + skip_list) return super(Array, self).buildShape(obj, pl, pls) @@ -428,42 +443,47 @@ def execute(self, obj): def rect_placements(base_placement, xvector, yvector, zvector, - xnum, ynum, znum): + xnum, ynum, znum, skip_list): """Determine the placements where the rectangular copies will be.""" pl = base_placement placements = [pl.copy()] - + count = 0 for xcount in range(xnum): currentxvector = App.Vector(xvector).multiply(xcount) if xcount != 0: - npl = pl.copy() - npl.translate(currentxvector) - placements.append(npl) + if count not in skip_list: + npl = pl.copy() + npl.translate(currentxvector) + placements.append(npl) for ycount in range(ynum): currentyvector = App.Vector(currentxvector) _y_shift = App.Vector(yvector).multiply(ycount) currentyvector = currentyvector.add(_y_shift) if ycount != 0: - npl = pl.copy() - npl.translate(currentyvector) - placements.append(npl) + if count not in skip_list: + npl = pl.copy() + npl.translate(currentyvector) + placements.append(npl) for zcount in range(znum): currentzvector = App.Vector(currentyvector) _z_shift = App.Vector(zvector).multiply(zcount) currentzvector = currentzvector.add(_z_shift) if zcount != 0: - npl = pl.copy() - npl.translate(currentzvector) - placements.append(npl) + if count not in skip_list: + npl = pl.copy() + npl.translate(currentzvector) + placements.append(npl) + print("count: ", count) + count += 1 return placements def polar_placements(base_placement, center, angle, - number, axis, axisvector): + number, axis, axisvector, skip_list): """Determine the placements where the polar copies will be.""" # print("angle ",angle," num ",num) placements = [base_placement.copy()] @@ -484,6 +504,8 @@ def polar_placements(base_placement, axis_tuple = DraftVecUtils.tup(axis) for i in range(number - 1): + if i in skip_list: + continue currangle = fraction + (i*fraction) npl = pl.copy() npl.rotate(center_tuple, axis_tuple, currangle) @@ -510,6 +532,7 @@ def circ_placements(base_placement, direction = axis.cross(App.Vector(lead)).normalize() placements = [base_placement.copy()] + count = 0 for xcount in range(1, circle_number): rc = xcount * r_distance c = 2 * rc * math.pi @@ -520,14 +543,76 @@ def circ_placements(base_placement, angle = 360.0/n for ycount in range(0, n): - npl = base_placement.copy() - trans = App.Vector(direction).multiply(rc) - npl.translate(trans) - npl.rotate(npl.Rotation.inverted().multVec(center-trans), - axis, - ycount * angle) - placements.append(npl) + if count not in skip_list: + npl = base_placement.copy() + trans = App.Vector(direction).multiply(rc) + npl.translate(trans) + npl.rotate(npl.Rotation.inverted().multVec(center-trans), + axis, + ycount * angle) + placements.append(npl) + count += 1 return placements + +def parse_skip_list(skip_expression): + """ + Parses a skip expression (as found in obj.Skip) as a list of integers. + + The reason for parsing this ourselves instead of using PropertyIntegerList + is so that we can handle failure silently, and support more advanced indexing. + + Syntax: + ======== + + Skip expressions are expanded in a python like way + + Example 1: skip_expression = 1:5 + Will be expanded to [1, 2, 3, 4] + + Example 2: skip_expression = [2:5, 9] + Will be expanded to [2, 3, 4, 9] + + Example 3: skip_expression = [1, 3:7:2, 9] + Will be expanded to [1, 3, 5, 9] + """ + + matches = re.finditer(r"(?P-?\d+):(?P-?\d*)(:(?P-?\d+))*", skip_expression) + + skip_list = [] + + if matches: + for m in matches: + d = m.groupdict() + d = {k:None if d[k] is None else int(d[k]) for k in d} + + # some input checking + if d['A'] < 0 or d['B'] < 0 or (d['C'] is not None and d['C'] < 0): + App.Console.PrintError("Draft-Array parse Skip expression: Negative index not supported yet. -> skipping pattern\n") + else: + if d['C']: + skip_list.extend(range(d['A'], d['B'], d['C'])) + else: + skip_list.extend(range(d['A'], d['B'])) + + skip_expression = skip_expression.replace(m.group(), '-0.1') # hack: we use a float as a marker in order have valid json, but not a valid index. + + try: + skip_list_raw = json.loads(skip_expression) + except json.decoder.JSONDecodeError: + return skip_list + + if not isinstance(skip_list_raw, list): + return skip_list + + for s in skip_list_raw: + if isinstance(s, int): + if s < 0: + App.Console.PrintError("Draft-Array-Skip parse Skip expression: Negative index not supported yet. -> skipping pattern") + continue + skip_list.append(s) + + return skip_list + ## @}