# -*- coding: utf-8 -*- __title__ = "Split curve" __author__ = "Christophe Grellier (Chris_G)" __license__ = "LGPL 2.1" __doc__ = "Splits the selected edge." import sys if sys.version_info.major >= 3: from importlib import reload import os import FreeCAD import FreeCADGui import Part from freecad.Curves import _utils from freecad.Curves import ICONPATH from pivy import coin from freecad.Curves import graphics from freecad.Curves import manipulators TOOL_ICON = os.path.join(ICONPATH, 'splitcurve.svg') #debug = _utils.debug debug = _utils.doNothing class split: """Splits the selected edge.""" def __init__(self, obj, e): obj.Proxy = self obj.addProperty("App::PropertyLinkSub", "Source", "Split", "Edge to split").Source = e obj.addProperty("App::PropertyStringList", "Values", "Split", "List of splitting locations\n% and units are allowed\nNegative values are computed from edge end") #obj.addProperty("App::PropertyFloatList", #"Parameters", #"Split", #"Parameter list") #obj.setEditorMode("Parameters",2) def getShape(self, fp): if fp.Source is None: return None, None if fp.Source[1] == []: # No subshape given, take wire 1 if fp.Source[0].Shape.Wires: w = fp.Source[0].Shape.Wire1 e = w.approximate(1e-7, 1e-5, len(w.Edges), 7).toShape() #double tol2d = gp::Resolution(); #double tol3d = 0.0001; #int maxseg=10, maxdeg=3; #static char* kwds_approx[] = {"Tol2d","Tol3d","MaxSegments","MaxDegree",NULL}; else: return None, None else: e = _utils.getShape(fp, "Source", "Edge") w = False return e, w def parse_value(self, edge, v): num_val = None par = None if "%" in v: num_val = float(v.split("%")[0]) * edge.Length / 100 t = '%' else: num_val = FreeCAD.Units.parseQuantity(v).Value t = FreeCAD.Units.Unit(v).Type if t == '': par = num_val elif num_val < 0: par = edge.Curve.parameterAtDistance(num_val, edge.LastParameter) else: par = edge.Curve.parameterAtDistance(num_val, edge.FirstParameter) if par > edge.FirstParameter and par < edge.LastParameter: return par, t def parse_values(self, edge, values): #edge = self.getShape(fp, "Source", "Edge") if not edge: return parameters = [] for v in values: par, t = self.parse_value(edge, v) parameters.append(par) parameters.sort() return parameters def onChanged(self, fp, prop): e = None if hasattr(fp, "Source") and fp.Source: e, w = self.getShape(fp) if not e: return if prop == "Source": debug("Split : Source changed") self.execute(fp) if prop == "Values": debug("Split : Values changed") self.execute(fp) def execute(self, obj): e, w = self.getShape(obj) params = [] if hasattr(obj, "Values"): params = self.parse_values(e, obj.Values) if params == []: if w: obj.Shape = obj.Source[0].Shape else: obj.Shape = e return if params[0] > e.FirstParameter: params.insert(0, e.FirstParameter) if params[-1] < e.LastParameter: params.append(e.LastParameter) if w: # No subshape given, take wire 1 edges = w.Edges for i in range(len(params)): p = e.valueAt(params[i]) d, pts, info = Part.Vertex(p).distToShape(w) #print(info) if info[0][3] == "Edge": n = info[0][4] nw = w.Edges[n].split(info[0][5]) nw.Placement = w.Edges[n].Placement if len(nw.Edges) == 2: edges[n] = nw.Edges[0] edges.insert(n+1,nw.Edges[1]) #print([e.Length for e in edges]) se = Part.sortEdges(edges) if len(se) > 1: FreeCAD.Console.PrintError("Split curve : failed to build temp Wire !") #print(se) w = Part.Wire(se[0]) else: edges = [] for i in range(len(params)-1): c = e.Curve.trim(params[i], params[i+1]) edges.append(c.toShape()) se = Part.sortEdges(edges) if len(se) > 1: FreeCAD.Console.PrintError("Split curve : failed to build final Wire !") wires = [] for el in se: wires.append(Part.Wire(el)) w = Part.Compound(wires) else: w = Part.Wire(se[0]) if w.isValid(): obj.Shape = w else: FreeCAD.Console.PrintError("Split curve : Invalid Wire !") obj.Shape = e class MarkerOnEdge(graphics.Marker): def __init__(self, points, sh=None): super(MarkerOnEdge, self).__init__(points, True) self._shape = None self._sublink = None self._tangent = None self._text_type = 0 self._text_translate = coin.SoTranslation() self._text_font = coin.SoFont() self._text_font.name = "Arial:Bold" self._text_font.size = 13.0 self._text = coin.SoText2() self._text_switch = coin.SoSwitch() self._text_switch.whichChild = coin.SO_SWITCH_ALL self._text_switch.addChild(self._text_translate) self._text_switch.addChild(self._text_font) self._text_switch.addChild(self._text) #self.on_drag_start.append(self.add_text) #self.on_drag_release.append(self.remove_text) self.on_drag.append(self.update_text) self.update_text() self.addChild(self._text_switch) if isinstance(sh,Part.Shape): self.snap_shape = sh elif isinstance(sh,(tuple,list)): self.sublink = sh def add_text(self): self._text_switch.whichChild = coin.SO_SWITCH_ALL self.on_drag.append(self.update_text) def remove_text(self): self._text_switch.whichChild = coin.SO_SWITCH_NONE self.on_drag.remove(self.update_text) def update_text(self): if self._shape is None: return p = self.points[0] par = self._shape.Curve.parameter(FreeCAD.Vector(p[0],p[1],p[2])) if self._text_type == 0 : coords = ['{: 9.3f}'.format(par)] else: if par <= self._shape.FirstParameter: abscissa = 0 else: c = self._shape.Curve.trim(self._shape.FirstParameter, par) abscissa = c.length() if self._text_type == 1 : coords = ['{: 9.3f} mm'.format(abscissa)] elif self._text_type == 2 : perc = 100 * abscissa / self._shape.Length coords = ['{: 9.3f} %'.format(perc)] self._text_translate.translation = p self._text.string.setValues(0,len(coords),coords) @property def tangent(self): return self._tangent @tangent.setter def tangent(self, t): if isinstance(t,FreeCAD.Vector): if t.Length > 1e-7: self._tangent = t self._tangent.normalize() self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 else: self._tangent = None self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 else: self._tangent = None self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 @property def snap_shape(self): return self._shape @snap_shape.setter def snap_shape(self, sh): if isinstance(sh,Part.Shape): self._shape = sh else: self._shape = None # self.alter_color() @property def sublink(self): return self._sublink @sublink.setter def sublink(self, sl): if isinstance(sl,(tuple,list)) and not (sl == self._sublink): self._shape = subshape_from_sublink(sl) self._sublink = sl else: self._shape = None self._sublink = None # self.alter_color() def alter_color(self): if isinstance(self._shape, Part.Vertex): self.set_color("white") elif isinstance(self._shape, Part.Edge): self.set_color("cyan") elif isinstance(self._shape, Part.Face): self.set_color("magenta") else: self.set_color("black") def __repr__(self): return("MarkerOnShape(%s)"%self._shape) def drag(self, mouse_coords, fact=1.): if self.enabled: pts = self.points for i, p in enumerate(pts): p[0] = mouse_coords[0] * fact + self._tmp_points[i][0] p[1] = mouse_coords[1] * fact + self._tmp_points[i][1] p[2] = mouse_coords[2] * fact + self._tmp_points[i][2] if self._shape: v = Part.Vertex(p[0],p[1],p[2]) proj = v.distToShape(self._shape)[1][0][1] # FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(),proj)) p[0] = proj.x p[1] = proj.y p[2] = proj.z self.points = pts for foo in self.on_drag: foo() class pointEditor(object): """Interpolation curve free-hand editor my_editor = pointEditor([points],obj) obj is the FreeCAD object that will receive the curve shape at the end of editing. points can be : - Vector (free point) - (Vector, shape) (point on shape)""" def __init__(self, points=[], fp = None): self.points = list() self.fp = fp self.curve = None self.root_inserted = False self.ctrl_keys = {"i" : [self.insert], "v" : [self.text_change], "q" : [self.quit], "\uffff" : [self.remove_point]} for p in points: if isinstance(p,FreeCAD.Vector): self.points.append(MarkerOnEdge(p)) elif isinstance(p,(tuple,list)): self.points.append(MarkerOnEdge(p[0],p[1])) elif isinstance(p, MarkerOnEdge): self.points.append(p) else: FreeCAD.Console.PrintError("pointEditor : bad input") for p in points: if hasattr(p, "ctrl_keys"): for key in p.ctrl_keys: if key in self.ctrl_keys: #print(key) self.ctrl_keys[key].extend(p.ctrl_keys[key]) else: self.ctrl_keys[key] = p.ctrl_keys[key] # Setup coin objects if self.fp: self.guidoc = self.fp.ViewObject.Document else: if not FreeCADGui.ActiveDocument: appdoc = FreeCAD.newDocument("New") self.guidoc = FreeCADGui.ActiveDocument self.view = self.guidoc.ActiveView self.rm = self.view.getViewer().getSoRenderManager() self.sg = self.view.getSceneGraph() self.setup_InteractionSeparator() def setup_InteractionSeparator(self): if self.root_inserted: self.sg.removeChild(self.root) self.root = graphics.InteractionSeparator(self.rm) self.root.setName("InteractionSeparator") #self.root.ovr_col = "yellow" #self.root.sel_col = "green" self.root.pick_radius = 40 #self.root.on_drag.append(self.update_curve) # Keyboard callback #self.events = coin.SoEventCallback() self._controlCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.controlCB) # populate root node #self.root.addChild(self.events) self.root += self.points #self.build_lines() #self.root += self.lines # set FreeCAD color scheme for o in self.points: # + self.lines: o.ovr_col = "yellow" o.sel_col = "green" self.root.register() self.sg.addChild(self.root) self.root_inserted = True self.root.selected_objects = list() def build_lines(self): self.lines = list() for m in self.points: if isinstance(m, MarkerOnEdge): line = Line([m.parent, m]) line.dynamic = False line.set_color("blue") self.lines.append(line) def controlCB(self, attr, event_callback): event = event_callback.getEvent() if event.getState() == event.UP: #FreeCAD.Console.PrintMessage("Key pressed : %s\n"%event.getKey()) if chr(event.getKey()) in self.ctrl_keys: for foo in self.ctrl_keys[chr(event.getKey())]: if foo.__self__ is self: foo() elif foo.__self__.parent in self.root.selected_objects: foo() def remove_point(self): pts = list() for o in self.root.dynamic_objects: if isinstance(o,MarkerOnEdge): pts.append(o) self.points = pts self.setup_InteractionSeparator() def insert(self): # get selected lines and subdivide them # pts = [] for o in self.root.selected_objects: #p1 = o.points[0] mark = MarkerOnEdge(o.points, o.snap_shape) self.points.append(mark) #new_select.append(mark) #self.points.append(pts) self.setup_InteractionSeparator() #self.root.selected_objects = new_select return True def text_change(self): for o in self.root.selected_objects: if o._text_type == 2: o._text_type = 0 else: o._text_type += 1 def quit(self): self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._controlCB) self.root.unregister() #self.root.removeAllChildren() self.sg.removeChild(self.root) self.root_inserted = False class splitVP: def __init__(self,vobj): vobj.Proxy = self self.select_state = True self.active = False self.ps = 0.0 def getIcon(self): return(TOOL_ICON) def attach(self, vobj): self.Object = vobj.Object self.active = False self.select_state = vobj.Selectable self.ip = None self.ps = 0.0 def setEdit(self,vobj,mode=0): if mode == 0: if vobj.Selectable: self.select_state = True vobj.Selectable = False self.ps = vobj.PointSize vobj.PointSize = 0.0 sl = self.Object.Source e,w = self.Object.Proxy.getShape(self.Object) params = [] if hasattr(self.Object, "Values"): params = self.Object.Proxy.parse_values(e, self.Object.Values) if params == []: return False pts = list() #print("Creating markers") if hasattr(self.Object, "Values"): for v in self.Object.Values: p,t = self.Object.Proxy.parse_value(e, v) #print("{} -> {}".format(p, e.valueAt(p))) m = MarkerOnEdge([e.valueAt(p)], e) if t == '': m._text_type = 0 elif t == '%': m._text_type = 2 else: m._text_type = 1 #m = manipulators.EdgeSnapAndTangent(e.valueAt(p), e) pts.append(m) #print(pts) self.ip = pointEditor(pts, self.Object) #self.ip.curve = e.Curve #vobj.Visibility = False self.active = True #print("Edit setup OK") return True return False def unsetEdit(self,vobj,mode=0): e,w = self.Object.Proxy.getShape(self.Object) if isinstance(self.ip, pointEditor): params = list() for p in self.ip.points: if isinstance(p, MarkerOnEdge): pt = p.points[0] par = e.Curve.parameter(FreeCAD.Vector(pt)) temp = e.Curve.trim(e.FirstParameter, par) #value = p._text.string.getValues()[0] #print(value) if p._text_type == 0: value = str(par) elif p._text_type == 1: value = "{:.3f}mm".format(temp.length()) elif p._text_type == 2: value = "{:.3f}%".format(100 * temp.length() / e.Length) params.append(value) self.Object.Values = params vobj.Selectable = self.select_state vobj.PointSize = self.ps self.ip.quit() self.ip = None self.active = False #vobj.Visibility = True return True def doubleClicked(self,vobj): if not hasattr(self,'active'): self.active = False if not self.active: self.active = True #self.setEdit(vobj) vobj.Document.setEdit(vobj) else: vobj.Document.resetEdit() self.active = False return True def __getstate__(self): return({"name": self.Object.Name}) def __setstate__(self,state): self.Object = FreeCAD.ActiveDocument.getObject(state["name"]) return(None) def claimChildren(self): if self.Object.Source: return [self.Object.Source[0]] def onDelete(self, feature, subelements): if self.Object.Source and hasattr(self.Object.Source[0],"ViewObject"): self.Object.Source[0].ViewObject.Visibility = True return True class splitCommand: """Splits the selected edges.""" def makeSplitFeature(self,e): splitCurve = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","SplitCurve") split(splitCurve, e) splitVP(splitCurve.ViewObject) FreeCAD.ActiveDocument.recompute() splitCurve.Values = ["50%"] splitCurve.ViewObject.PointSize = 5.0 def Activated(self): edges = [] sel = FreeCADGui.Selection.getSelectionEx() if sel == []: FreeCAD.Console.PrintError("Select the edges to split first !\n") for selobj in sel: if selobj.HasSubObjects: for i in range(len(selobj.SubObjects)): if isinstance(selobj.SubObjects[i], Part.Edge): self.makeSplitFeature((selobj.Object, selobj.SubElementNames[i])) if selobj.Object.Shape: if len(selobj.Object.Shape.Edges) == 1: selobj.Object.ViewObject.Visibility = False else: self.makeSplitFeature((selobj.Object, [])) if hasattr(selobj.Object,"ViewObject"): selobj.Object.ViewObject.Visibility = False def IsActive(self): if FreeCAD.ActiveDocument and FreeCADGui.Selection.getSelectionEx(): return True else: return False def GetResources(self): return {'Pixmap' : TOOL_ICON, 'MenuText': 'Split Curve', 'ToolTip': 'Splits the selected edge'} FreeCADGui.addCommand('split', splitCommand())