From 7d4b1843940decc57842497d58203060c8d248f8 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 17 Aug 2018 19:10:03 +0800 Subject: [PATCH] constraint: optimize constraint multiplication --- assembly.py | 16 +++++++++++---- constraint.py | 54 +++++++++++++++++++++++++++++++++----------------- solver.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++--- system.py | 2 +- 4 files changed, 101 insertions(+), 26 deletions(-) diff --git a/assembly.py b/assembly.py index 94011af..411d81f 100644 --- a/assembly.py +++ b/assembly.py @@ -365,12 +365,20 @@ class AsmElement(AsmBase): else: parentShape = Part.getShape(info.Part, info.Subname, transform=False, needSubElement=False) - shapes = [] + found = False + shapes = [info.Shape] + pla = info.Shape.Placement for edge in parentShape.Edges: - if info.Shape.isCoplanar(edge) and \ - utils.isSameValue( + if not info.Shape.isCoplanar(edge) or \ + not utils.isSameValue( utils.getElementCircular(edge,True),obj.Radius): - edge.transformShape(mat,True) + continue + edge.transformShape(mat,True) + if not found and utils.isSamePlacement(pla,edge.Placement): + found = True + # make sure the direct referenced edge is the first one + shapes[0] = edge + else: shapes.append(edge) shape = shapes diff --git a/constraint.py b/constraint.py index 5b001cf..4ddbc04 100644 --- a/constraint.py +++ b/constraint.py @@ -934,25 +934,43 @@ class BaseMulti(Base): logger.warn('{} no first part shape'.format(cstrName(obj))) return idx = 0 + updates = [] for element in elements[1:]: - for info in element.Proxy.getInfo(expand=True): - info0 = firstInfo[idx] - partInfo0 = solver.getPartInfo(info0) - partInfo = solver.getPartInfo(info) - e0 = cls._entityDef[0]( - solver,partInfo0,info0.Subname,info0.Shape) - e = cls._entityDef[0]( - solver,partInfo,info.Subname,info.Shape) - params = props + [e0,e] - solver.system.checkRedundancy(obj,partInfo0,partInfo) - h = func(*params,group=solver.group) - if isinstance(h,(list,tuple)): - ret += list(h) - else: - ret.append(h) - idx += 1 - if idx >= count: - return ret + infos = element.Proxy.getInfo(expand=True) + if not infos: + continue + info0 = firstInfo[idx] + partInfo0 = solver.getPartInfo(info0,infos) + info = infos[0] + partInfo = solver.getPartInfo(info) + e0 = cls._entityDef[0]( + solver,partInfo0,info0.Subname,info0.Shape) + e = cls._entityDef[0]( + solver,partInfo,info.Subname,info.Shape) + params = props + [e0,e] + solver.system.checkRedundancy(obj,partInfo0,partInfo) + h = func(*params,group=solver.group) + if isinstance(h,(list,tuple)): + ret += list(h) + else: + ret.append(h) + idx += 1 + if idx >= count: + return ret + if len(infos)>1: + updates.append((partInfo0,element,len(infos)-1)) + + for partInfo0,element,infoCount in updates: + if partInfo0.Update: + logger.warn('{} used in more than one constraint ' + 'multiplication'.format(partInfo0.PartName)) + continue + partInfo0.Update.append(idx) + partInfo0.Update.append(element) + idx += infoCount + if idx >= count: + break + return ret parts = set() diff --git a/solver.py b/solver.py index 9c14b61..7c04d6d 100644 --- a/solver.py +++ b/solver.py @@ -1,5 +1,5 @@ import random, math -from collections import namedtuple +from collections import namedtuple,defaultdict import FreeCAD, FreeCADGui from .assembly import Assembly, isTypeOf, setPlacement from . import utils @@ -21,8 +21,12 @@ from .system import System # CstrMap: map from other part to the constrains between this and the othe part. # This is for auto constraint DOF reduction. Only some composite # constraints will be mapped. +# Update: in case the constraint uses the `Multiplication` feature, only the +# first element of all the coplanar edges will be actually constrainted. +# The rest ElementInfo will be stored here for later update by matrix +# transformation. PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement', - 'Params','Workplane','EntityMap','Group','CstrMap')) + 'Params','Workplane','EntityMap','Group','CstrMap','Update')) class Solver(object): def __init__(self,assembly,reportFailed,dragPart,recompute,rollback): @@ -116,6 +120,7 @@ class Solver(object): self.system.log('done solving') touched = False + updates = [] for part,partInfo in self._partMap.items(): if part in self._fixedParts: continue @@ -147,6 +152,8 @@ class Solver(object): touched = True part.Points = points else: + if partInfo.Update: + updates.append(partInfo) params = [self.system.getParam(h).val for h in partInfo.Params] p = params[:3] q = (params[4],params[5],params[6],params[3]) @@ -161,6 +168,8 @@ class Solver(object): rollback.append((partInfo.PartName, part, partInfo.Placement.copy())) + partInfo.Placement.Base = pla.Base + partInfo.Placement.Rotation = pla.Rotation setPlacement(part,pla) if utils.isDraftCircle(part): @@ -198,6 +207,45 @@ class Solver(object): if recompute and touched: assembly.recompute(True) + # Update parts with constraint multiplication, which auto expands + # coplanar circular edges of the same radius. For performance sake, only + # the first edge of each expansion is used for constraint. We simply + # translate the rest of the parts with the same relative offset. + touches = defaultdict(set) + for partInfo in updates: + idx,element = partInfo.Update + element0 = element.Proxy.parent.Object.Group[0] + infos0 = element0.Proxy.getInfo(expand=True) + count = len(infos0) + infos = element.Proxy.getInfo(expand=True) + pos0 = infos[0].Placement.multVec( + utils.getElementPos(infos[0].Shape)) + touched = False + for info in infos[1:]: + if idx >= count: + break + pos = info.Placement.multVec( + utils.getElementPos(info.Shape)) + pla = partInfo.Placement.copy() + pla.Base += pos-pos0 + info0 = infos0[idx] + idx += 1 + if isSamePlacement(info0.Placement,pla): + self.system.log('not moving {}'.format(info0.PartName)) + else: + self.system.log('moving {} {}'.format( + partInfo.PartName,pla)) + touched = True + if rollback is not None: + rollback.append((info0.PartName, + info0.Part, + info0.Placement.copy())) + setPlacement(info0.Part,pla) + if touched: + touches[partInfo.Part[0].Document].add(partInfo.Part[0]) + for doc,objs in touches.items(): + doc.recompute(list(objs)) + def isFixedPart(self,part): if isinstance(part,tuple) and part[0] in self._fixedParts: return True @@ -257,7 +305,8 @@ class Solver(object): Workplane = h, EntityMap = {}, Group = group if group else g, - CstrMap = {}) + CstrMap = {}, + Update = []) self.system.log('{}, {}'.format(partInfo,g)) diff --git a/system.py b/system.py index 3066d3b..b44c5f3 100644 --- a/system.py +++ b/system.py @@ -138,7 +138,7 @@ class SystemExtension(object): if not yaw and not pitch and not roll: n = n2.entity else: - rot = n2.rot.multiply(FreeCAD.Rotation(yaw,pitch,roll)) + rot = FreeCAD.Rotation(yaw,pitch,roll).multiply(n2.rot) e = self.addNormal3dV(*getNormal(rot)) n = self.addTransform(e,*n2.params,group=group) h.append(self.addSameOrientation(n1.entity,n,group=group))