| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides utility functions that deal with GUI interactions. |
| | |
| | This module contains auxiliary functions which can be used |
| | in other modules of the workbench, and which require |
| | the graphical user interface (GUI), as they access the view providers |
| | of the objects or the 3D view. |
| | """ |
| | |
| | |
| | |
| |
|
| | |
| | |
| | import importlib |
| | import math |
| | import os |
| |
|
| | import FreeCAD as App |
| | from draftutils import params |
| | from draftutils import utils |
| | from draftutils.messages import _err, _wrn |
| | from draftutils.translate import translate |
| |
|
| | if App.GuiUp: |
| | import FreeCADGui as Gui |
| | from pivy import coin |
| | from PySide import QtCore |
| | from PySide import QtGui |
| |
|
| | |
| |
|
| |
|
| | def get_3d_view(): |
| | """Return the current 3D view. |
| | |
| | Returns |
| | ------- |
| | Gui::View3DInventor |
| | The Active 3D View or `None`. |
| | """ |
| | if not App.GuiUp: |
| | return None |
| |
|
| | |
| | |
| | import FreeCADGui as Gui |
| | from pivy import coin |
| |
|
| | mw = Gui.getMainWindow() |
| | view = mw.getActiveWindow() |
| | if view is None: |
| | return None |
| | if not hasattr(view, "getSceneGraph"): |
| | return None |
| | return view |
| |
|
| |
|
| | get3DView = get_3d_view |
| |
|
| |
|
| | def autogroup(obj): |
| | """Add a given object to the defined Draft autogroup, if applicable. |
| | |
| | This function only works if the graphical interface is available. |
| | It checks that the `App.draftToolBar` class is available, |
| | which contains the group to use to automatically store |
| | new created objects. |
| | |
| | Originally, it worked with standard groups (`App::DocumentObjectGroup`), |
| | and Arch Workbench containers like `'Site'`, `'Building'`, `'Floor'`, |
| | and `'BuildingPart'`. |
| | |
| | Now it works with Draft Layers. |
| | |
| | Parameters |
| | ---------- |
| | obj: App::DocumentObject |
| | Any type of object that will be stored in the group. |
| | """ |
| |
|
| | |
| | if not App.GuiUp: |
| | return |
| | if not hasattr(Gui, "draftToolBar"): |
| | return |
| | if not hasattr(Gui.draftToolBar, "autogroup"): |
| | return |
| | if Gui.draftToolBar.isConstructionMode(): |
| | return |
| |
|
| | |
| | |
| | for par in App.ActiveDocument.findObjects(Type="App::GeometryPython"): |
| | if hasattr(par.Proxy, "autogroup"): |
| | if par.Proxy.autogroup(par, obj): |
| | return |
| |
|
| | |
| | if Gui.draftToolBar.autogroup is not None: |
| | active_group = App.ActiveDocument.getObject(Gui.draftToolBar.autogroup) |
| | if active_group is None: |
| | |
| | Gui.draftToolBar.setAutoGroup() |
| | return |
| | if obj in active_group.InListRecursive: |
| | return |
| | if not obj in active_group.Group: |
| | active_group.Group += [obj] |
| |
|
| | elif Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") is not None: |
| | |
| | try: |
| | from nativeifc import ifc_tools |
| |
|
| | parent = Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") |
| | ifc_tools.aggregate(obj, parent) |
| | except: |
| | pass |
| |
|
| | elif Gui.ActiveDocument.ActiveView.getActiveObject("Arch") is not None: |
| | |
| | active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch") |
| | if obj in active_arch_obj.InListRecursive: |
| | |
| | return |
| | active_arch_obj.addObject(obj) |
| |
|
| | elif Gui.ActiveDocument.ActiveView.getActiveObject("part") is not None: |
| | |
| | |
| | |
| | active_part, parent, sub = Gui.ActiveDocument.ActiveView.getActiveObject("part", False) |
| | if obj in active_part.InListRecursive: |
| | |
| | return |
| | matrix = parent.getSubObject(sub, retType=4) |
| | if matrix.hasScale() == App.ScaleType.Uniform: |
| | err = translate("draft", "Unable to insert new object into " "a scaled part") |
| | App.Console.PrintMessage(err) |
| | return |
| | inverse_placement = App.Placement(matrix.inverse()) |
| | if utils.get_type(obj) == "Point": |
| | point_vector = App.Vector(obj.X, obj.Y, obj.Z) |
| | real_point = inverse_placement.multVec(point_vector) |
| | obj.X = real_point.x |
| | obj.Y = real_point.y |
| | obj.Z = real_point.z |
| | elif utils.get_type(obj) in ["Dimension", "LinearDimension"]: |
| | obj.Start = inverse_placement.multVec(obj.Start) |
| | obj.End = inverse_placement.multVec(obj.End) |
| | obj.Dimline = inverse_placement.multVec(obj.Dimline) |
| | obj.Normal = inverse_placement.Rotation.multVec(obj.Normal) |
| | obj.Direction = inverse_placement.Rotation.multVec(obj.Direction) |
| | elif utils.get_type(obj) in ["Label"]: |
| | obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement)) |
| | obj.TargetPoint = inverse_placement.multVec(obj.TargetPoint) |
| | elif hasattr(obj, "Placement"): |
| | |
| | obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement)) |
| |
|
| | active_part.addObject(obj) |
| |
|
| |
|
| | def dim_symbol(symbol=None, invert=False): |
| | """Return the specified dimension symbol. |
| | |
| | Parameters |
| | ---------- |
| | symbol: int, optional |
| | It defaults to `None`, in which it gets the value from the parameter |
| | database, `get_param("dimsymbolend")`. |
| | |
| | A numerical value defines different markers |
| | * 0, `SoSphere` |
| | * 1, `SoSeparator` with a `SoLineSet`, a circle (in fact a 24 sided polygon) |
| | * 2, `SoSeparator` with a `soCone` |
| | * 3, `SoSeparator` with a `SoFaceSet` |
| | * 4, `SoSeparator` with a `SoLineSet`, calling `dim_dash` |
| | * 5, Nothing |
| | * Otherwise, `SoSphere` |
| | |
| | invert: bool, optional |
| | It defaults to `False`. |
| | If it is `True` and `symbol=2`, the cone will be rotated |
| | -90 degrees around the Z axis, otherwise the rotation is positive, |
| | +90 degrees. |
| | |
| | Returns |
| | ------- |
| | Coin.SoNode |
| | A `Coin.SoSphere`, or `Coin.SoSeparator` (circle, cone, face, line) |
| | that will be used as a dimension symbol. |
| | """ |
| | if symbol is None: |
| | symbol = params.get_param("dimsymbolend") |
| |
|
| | if symbol == 0: |
| | |
| | |
| |
|
| | |
| | |
| | |
| | marker = coin.SoSphere() |
| | return marker |
| | elif symbol == 1: |
| | marker = coin.SoSeparator() |
| | v = coin.SoVertexProperty() |
| | for i in range(25): |
| | ang = math.radians(i * 15) |
| | v.vertex.set1Value(i, (math.sin(ang), math.cos(ang), 0)) |
| | p = coin.SoLineSet() |
| | p.vertexProperty = v |
| | marker.addChild(p) |
| | return marker |
| | elif symbol == 2: |
| | marker = coin.SoSeparator() |
| | t = coin.SoTransform() |
| | t.translation.setValue((0, -2, 0)) |
| | t.center.setValue((0, 2, 0)) |
| | if invert: |
| | t.rotation.setValue(coin.SbVec3f((0, 0, 1)), -math.pi / 2) |
| | else: |
| | t.rotation.setValue(coin.SbVec3f((0, 0, 1)), math.pi / 2) |
| | c = coin.SoCone() |
| | c.height.setValue(4) |
| | marker.addChild(t) |
| | marker.addChild(c) |
| | return marker |
| | elif symbol == 3: |
| | marker = coin.SoSeparator() |
| | |
| | h = coin.SoShapeHints() |
| | h.vertexOrdering = h.COUNTERCLOCKWISE |
| | c = coin.SoCoordinate3() |
| | c.point.setValues([(-1, -2, 0), (0, 2, 0), (1, 2, 0), (0, -2, 0)]) |
| | f = coin.SoFaceSet() |
| | marker.addChild(h) |
| | marker.addChild(c) |
| | marker.addChild(f) |
| | return marker |
| | elif symbol == 4: |
| | return dim_dash((-1.5, -1.5, 0), (1.5, 1.5, 0)) |
| | elif symbol == 5: |
| | return coin.SoSeparator() |
| | else: |
| | _wrn(translate("draft", "Symbol not implemented. Using a default symbol.")) |
| | return coin.SoSphere() |
| |
|
| |
|
| | dimSymbol = dim_symbol |
| |
|
| |
|
| | def dim_dash(p1, p2): |
| | """Return a SoSeparator with a line used to make dimension dashes. |
| | |
| | It is used by `dim_symbol` to create line end symbols |
| | like `'Tick-2'`, `'DimOvershoot'`, and `'ExtOvershoot'` dashes. |
| | |
| | Parameters |
| | ---------- |
| | p1: tuple of three floats or Base::Vector3 |
| | A point to define a line vertex. |
| | |
| | p2: tuple of three floats or Base::Vector3 |
| | A point to define a line vertex. |
| | |
| | Returns |
| | ------- |
| | Coin.SoSeparator |
| | A Coin object with a `SoLineSet` created from `p1` and `p2` |
| | as vertices. |
| | """ |
| | dash = coin.SoSeparator() |
| | v = coin.SoVertexProperty() |
| | v.vertex.set1Value(0, p1) |
| | v.vertex.set1Value(1, p2) |
| | line = coin.SoLineSet() |
| | line.vertexProperty = v |
| | dash.addChild(line) |
| | return dash |
| |
|
| |
|
| | dimDash = dim_dash |
| |
|
| |
|
| | def remove_hidden(objectslist): |
| | """Return only the visible objects in the list. |
| | |
| | This function only works if the graphical interface is available |
| | as the `Visibility` attribute is a property of the view provider |
| | (`obj.ViewObject`). |
| | |
| | Parameters |
| | ---------- |
| | objectslist: list of App::DocumentObject |
| | List of any type of object. |
| | |
| | Returns |
| | ------- |
| | list |
| | Return a copy of the input list without those objects |
| | for which `obj.ViewObject.Visibility` is `False`. |
| | |
| | If the graphical interface is not loaded |
| | the returned list is just a copy of the input list. |
| | """ |
| | newlist = objectslist[:] |
| | for obj in objectslist: |
| | if obj.ViewObject: |
| | if not obj.ViewObject.isVisible(): |
| | newlist.remove(obj) |
| | return newlist |
| |
|
| |
|
| | removeHidden = remove_hidden |
| |
|
| |
|
| | def get_diffuse_color(objs): |
| | """Get a (cumulative) diffuse color from one or more objects. |
| | |
| | If all tuples in the result are identical a list with a single tuple is |
| | returned. In theory all faces of an object can be set to the same diffuse |
| | color that is different from its shape color, but that seems rare. The |
| | function does not take that into account. |
| | |
| | Parameters |
| | ---------- |
| | objs: a single object or an iterable with objects. |
| | |
| | Returns |
| | ------- |
| | list of tuples |
| | The list will be empty if no valid object is found. |
| | """ |
| |
|
| | def _get_color(obj): |
| | if hasattr(obj, "ColoredElements"): |
| | if hasattr(obj, "Count") or hasattr(obj, "ElementCount"): |
| | |
| | if hasattr(obj, "Count"): |
| | count = obj.Count |
| | base = obj.Base |
| | else: |
| | count = obj.ElementCount if obj.ElementCount > 0 else 1 |
| | base = obj.LinkedObject |
| | if base is None: |
| | return [] |
| | cols = _get_color(base) * count |
| | if obj.ColoredElements is None: |
| | return cols |
| | face_num = len(base.Shape.Faces) |
| | for elm, override in zip(obj.ColoredElements[1], obj.ViewObject.OverrideColorList): |
| | if ( |
| | "Face" in elm |
| | ): |
| | if "." in elm: |
| | elm0, elm1 = elm.split(".") |
| | i = (int(elm0) * face_num) + int(elm1[4:]) - 1 |
| | cols[i] = override |
| | else: |
| | i = int(elm[4:]) - 1 |
| | for j in range(count): |
| | cols[(j * face_num) + i] = override |
| | return cols |
| | elif hasattr(obj, "ElementList"): |
| | |
| | cols = [] |
| | for sub in obj.ElementList: |
| | sub_cols = _get_color(sub) |
| | if obj.ColoredElements is None: |
| | cols += sub_cols |
| | else: |
| | for elm, override in zip( |
| | obj.ColoredElements[1], obj.ViewObject.OverrideColorList |
| | ): |
| | if sub.Name + ".Face" in elm: |
| | i = int(elm[(len(sub.Name) + 5) :]) - 1 |
| | sub_cols[i] = override |
| | cols += sub_cols |
| | return cols |
| | else: |
| | return [] |
| | elif hasattr(obj.ViewObject, "DiffuseColor"): |
| | if len(obj.ViewObject.DiffuseColor) == len(obj.Shape.Faces): |
| | return obj.ViewObject.DiffuseColor |
| | else: |
| | col = obj.ViewObject.ShapeColor |
| | col = (col[0], col[1], col[2], 1.0 - obj.ViewObject.Transparency / 100.0) |
| | return [col] * len(obj.Shape.Faces) |
| | elif obj.hasExtension("App::GeoFeatureGroupExtension"): |
| | cols = [] |
| | for sub in obj.Group: |
| | cols += _get_color(sub) |
| | return cols |
| | else: |
| | return [] |
| |
|
| | if not isinstance(objs, list): |
| | |
| | obj = objs |
| | if ( |
| | not hasattr(obj, "ColoredElements") |
| | and hasattr(obj.ViewObject, "DiffuseColor") |
| | and ( |
| | len(obj.ViewObject.DiffuseColor) == 1 |
| | or len(obj.ViewObject.DiffuseColor) == len(obj.Shape.Faces) |
| | ) |
| | ): |
| | return obj.ViewObject.DiffuseColor |
| | |
| | objs = [objs] |
| |
|
| | colors = [] |
| | for obj in objs: |
| | colors += _get_color(obj) |
| |
|
| | if len(colors) > 1: |
| | first = colors[0] |
| | for next in colors[1:]: |
| | if next != first: |
| | break |
| | else: |
| | colors = [first] |
| | return colors |
| |
|
| |
|
| | def apply_current_style(objs): |
| | """Apply the current style to one or more objects. |
| | |
| | Parameters |
| | ---------- |
| | objs: a single object or an iterable with objects. |
| | """ |
| | if not isinstance(objs, list): |
| | objs = [objs] |
| | anno_style = utils.get_default_annotation_style() |
| | shape_style = utils.get_default_shape_style() |
| | for obj in objs: |
| | if not hasattr(obj, "ViewObject"): |
| | continue |
| | vobj = obj.ViewObject |
| | props = vobj.PropertiesList |
| | style = anno_style if ("FontName" in props) else shape_style |
| | for prop in props: |
| | if prop in style: |
| | if style[prop][0] == "index": |
| | if style[prop][2] in vobj.getEnumerationsOfProperty(prop): |
| | setattr(vobj, prop, style[prop][2]) |
| | else: |
| | setattr(vobj, prop, style[prop][1]) |
| |
|
| |
|
| | def restore_view_object(obj, vp_module, vp_class, format=True, format_ref=None): |
| | """Restore the ViewObject if the object was saved without the GUI. |
| | |
| | Parameters |
| | ---------- |
| | obj: App::DocumentObject |
| | Object whose ViewObject needs to be restored. |
| | |
| | vp_module: string |
| | View provider module. Must be in the draftviewproviders directory. |
| | |
| | vp_class: string |
| | View provider class. |
| | |
| | format: bool, optional |
| | Defaults to `True`. |
| | If `True` the `format_object` function is called to update the |
| | properties of the ViewObject. |
| | |
| | format_ref: App::DocumentObject, optional |
| | Defaults to `None`. |
| | Reference object to copy ViewObject properties from. |
| | """ |
| | if not getattr(obj, "ViewObject", None): |
| | return |
| | vobj = obj.ViewObject |
| | if not getattr(vobj, "Proxy", None): |
| | vp_module = importlib.import_module("draftviewproviders." + vp_module) |
| | getattr(vp_module, vp_class)(vobj) |
| | if format: |
| | format_object(obj, format_ref) |
| |
|
| |
|
| | def format_object(target, origin=None, ignore_construction=False): |
| | """Apply visual properties to an object. |
| | |
| | This function only works if the graphical interface is available. |
| | |
| | If origin is `None` and target is not an annotation, the DefaultDrawStyle |
| | and DefaultDisplayMode preferences are applied. Else, the properties of |
| | origin are applied to target. |
| | |
| | If construction mode is active target is then placed in the construction |
| | group and the `constr` color is applied to its applicable color properties: |
| | TextColor, PointColor, LineColor, and ShapeColor. |
| | |
| | Parameters |
| | ---------- |
| | target: App::DocumentObject |
| | |
| | origin: App::DocumentObject, optional |
| | Defaults to `None`. |
| | If construction mode is not active, its visual properties are assigned |
| | to `target`, with the exception of `BoundingBox`, `Proxy`, `RootNode` |
| | and `Visibility`. |
| | |
| | ignore_construction: bool, optional |
| | Defaults to `False`. |
| | Set to `True` to ignore construction mode. |
| | """ |
| | if not target: |
| | return |
| | if not App.GuiUp: |
| | return |
| | if not hasattr(Gui, "draftToolBar"): |
| | return |
| | if not hasattr(target, "ViewObject"): |
| | return |
| | if hasattr(target, "Shape") and target.Shape.Faces: |
| | len_faces = len(target.Shape.Faces) |
| | else: |
| | len_faces = 1 |
| | obrep = target.ViewObject |
| | obprops = obrep.PropertiesList |
| | if origin and hasattr(origin, "ViewObject"): |
| | matchrep = origin.ViewObject |
| | for p in matchrep.PropertiesList: |
| | if p in ("DisplayMode", "BoundingBox", "Proxy", "RootNode", "Visibility"): |
| | continue |
| | if p not in obprops: |
| | continue |
| | if obrep.getEditorMode(p): |
| | continue |
| | val = getattr(matchrep, p) |
| | if isinstance(val, tuple): |
| | if len(val) != len_faces: |
| | val = (val[0],) |
| | elif hasattr(val, "Value"): |
| | val = val.Value |
| | try: |
| | setattr(obrep, p, val) |
| | except Exception: |
| | pass |
| | if matchrep.DisplayMode in obrep.listDisplayModes(): |
| | obrep.DisplayMode = matchrep.DisplayMode |
| | if hasattr(obrep, "DiffuseColor"): |
| | difcol = get_diffuse_color(origin) |
| | if difcol and len(difcol) == len_faces: |
| | obrep.DiffuseColor = difcol |
| | elif "FontName" not in obprops: |
| | |
| | if "DrawStyle" in obprops: |
| | obrep.DrawStyle = utils.DRAW_STYLES[params.get_param("DefaultDrawStyle")] |
| | if "DisplayMode" in obprops: |
| | dm = utils.DISPLAY_MODES[params.get_param("DefaultDisplayMode")] |
| | if dm in obrep.listDisplayModes(): |
| | obrep.DisplayMode = dm |
| | if ignore_construction: |
| | return |
| | if Gui.draftToolBar.isConstructionMode(): |
| | doc = App.ActiveDocument |
| | col = params.get_param("constructioncolor") | 0x000000FF |
| | grp = doc.getObject("Draft_Construction") |
| | if not grp: |
| | grp = doc.addObject("App::DocumentObjectGroup", "Draft_Construction") |
| | grp.Label = params.get_param("constructiongroupname") |
| | grp.addObject(target) |
| | if "TextColor" in obprops: |
| | obrep.TextColor = col |
| | if "PointColor" in obprops: |
| | obrep.PointColor = col |
| | if "LineColor" in obprops: |
| | obrep.LineColor = col |
| | if "ShapeColor" in obprops: |
| | obrep.ShapeColor = col |
| | if hasattr(obrep, "Transparency"): |
| | obrep.Transparency = 80 |
| |
|
| |
|
| | formatObject = format_object |
| |
|
| |
|
| | def get_selection(gui=App.GuiUp): |
| | """Return the current selected objects. |
| | |
| | This function only works if the graphical interface is available |
| | as the selection module only works on the 3D view. |
| | |
| | It wraps around `Gui.Selection.getSelection` |
| | |
| | Parameters |
| | ---------- |
| | gui: bool, optional |
| | It defaults to the value of `App.GuiUp`, which is `True` |
| | when the interface exists, and `False` otherwise. |
| | |
| | This value can be set to `False` to simulate |
| | when the interface is not available. |
| | |
| | Returns |
| | ------- |
| | list of App::DocumentObject |
| | Returns a list of objects in the current selection. |
| | It can be an empty list if no object is selected. |
| | |
| | If the interface is not available, it returns `None`. |
| | """ |
| | if gui: |
| | return Gui.Selection.getSelection() |
| | return None |
| |
|
| |
|
| | getSelection = get_selection |
| |
|
| |
|
| | def get_selection_ex(gui=App.GuiUp): |
| | """Return the current selected objects together with their subelements. |
| | |
| | This function only works if the graphical interface is available |
| | as the selection module only works on the 3D view. |
| | |
| | It wraps around `Gui.Selection.getSelectionEx` |
| | |
| | Parameters |
| | ---------- |
| | gui: bool, optional |
| | It defaults to the value of `App.GuiUp`, which is `True` |
| | when the interface exists, and `False` otherwise. |
| | |
| | This value can be set to `False` to simulate |
| | when the interface is not available. |
| | |
| | Returns |
| | ------- |
| | list of Gui::SelectionObject |
| | Returns a list of `Gui::SelectionObject` in the current selection. |
| | It can be an empty list if no object is selected. |
| | |
| | If the interface is not available, it returns `None`. |
| | |
| | Selection objects |
| | ----------------- |
| | One `Gui::SelectionObject` has attributes that indicate which specific |
| | subelements, that is, vertices, wires, and faces, were selected. |
| | This can be useful to operate on the subelements themselves. |
| | If `G` is a `Gui::SelectionObject` |
| | * `G.Object` is the selected object |
| | * `G.ObjectName` is the name of the selected object |
| | * `G.HasSubObjects` is `True` if there are subelements in the selection |
| | * `G.SubObjects` is a tuple of the subelements' shapes |
| | * `G.SubElementNames` is a tuple of the subelements' names |
| | |
| | `SubObjects` and `SubElementNames` should be empty tuples |
| | if `HasSubObjects` is `False`. |
| | """ |
| | if gui: |
| | return Gui.Selection.getSelectionEx() |
| | return None |
| |
|
| |
|
| | getSelectionEx = get_selection_ex |
| |
|
| |
|
| | def select(objs=None, gui=App.GuiUp): |
| | """Unselects everything and selects only the given list of objects. |
| | |
| | This function only works if the graphical interface is available |
| | as the selection module only works on the 3D view. |
| | |
| | Parameters |
| | ---------- |
| | objs: list of App::DocumentObjects or tuples, or a single object or tuple, optional |
| | It defaults to `None`. |
| | Format for tuples: |
| | `(doc.Name or "", sel.Object.Name, sel.SubElementName or "")` |
| | For example (Box nested in Part): |
| | `("", "Part", "Box.Edge1")` |
| | |
| | gui: bool, optional |
| | It defaults to the value of `App.GuiUp`, which is `True` |
| | when the interface exists, and `False` otherwise. |
| | |
| | This value can be set to `False` to simulate |
| | when the interface is not available. |
| | """ |
| | if gui: |
| | Gui.Selection.clearSelection() |
| | if objs is not None: |
| | if not isinstance(objs, list): |
| | objs = [objs] |
| | for obj in objs: |
| | if not obj: |
| | continue |
| | if isinstance(obj, tuple): |
| | |
| | |
| | |
| | |
| | |
| | parent = App.ActiveDocument.getObject(obj[1]) |
| | if parent and parent.getSubObject(obj[2]): |
| | Gui.Selection.addSelection(*obj) |
| | continue |
| | if utils.is_deleted(obj): |
| | continue |
| | Gui.Selection.addSelection(obj) |
| |
|
| |
|
| | def load_texture(filename, size=None, gui=App.GuiUp): |
| | """Return a Coin.SoSFImage to use as a texture for a 2D plane. |
| | |
| | This function only works if the graphical interface is available |
| | as the visual properties that can be applied to a shape |
| | are attributes of the view provider (`obj.ViewObject`). |
| | |
| | Parameters |
| | ---------- |
| | filename: str |
| | A path to a pixel image file (PNG) that can be used as a texture |
| | on the face of an object. |
| | |
| | size: tuple of two int, or a single int, optional |
| | It defaults to `None`. |
| | If a tuple is given, the two values define the width and height |
| | in pixels to which the loaded image will be scaled. |
| | If it is a single value, it is used for both dimensions. |
| | |
| | If it is `None`, the size will be determined from the `QImage` |
| | created from `filename`. |
| | |
| | CURRENTLY the input `size` parameter IS NOT USED. |
| | It always uses the `QImage` to determine this information. |
| | |
| | gui: bool, optional |
| | It defaults to the value of `App.GuiUp`, which is `True` |
| | when the interface exists, and `False` otherwise. |
| | |
| | This value can be set to `False` to simulate |
| | when the interface is not available. |
| | |
| | Returns |
| | ------- |
| | coin.SoSFImage |
| | An image object with the appropriate size, number of components |
| | (grayscale, grayscale and transparency, color, |
| | color and transparency), and byte data. |
| | |
| | It returns `None` if the interface is not available, |
| | or if there is a problem creating the image. |
| | """ |
| | if gui: |
| | |
| | |
| | try: |
| | p = QtGui.QImage(filename) |
| |
|
| | if p.isNull(): |
| | _wrn("load_texture: " + translate("draft", "image is Null")) |
| |
|
| | if not os.path.exists(filename): |
| | raise FileNotFoundError( |
| | -1, |
| | translate( |
| | "draft", |
| | "filename does not exist " "on the system or " "in the resource file", |
| | ), |
| | filename, |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | size = coin.SbVec2s(p.width(), p.height()) |
| | buffersize = p.sizeInBytes() |
| | width = size[0] |
| | height = size[1] |
| | numcomponents = int(buffersize / (width * height)) |
| |
|
| | img = coin.SoSFImage() |
| | byteList = bytearray() |
| |
|
| | |
| | |
| | |
| | |
| | for y in range(height): |
| | |
| | for x in range(width): |
| | rgba = p.pixel(x, y) |
| | if numcomponents <= 2: |
| | byteList.append(QtGui.qGray(rgba)) |
| |
|
| | if numcomponents == 2: |
| | byteList.append(QtGui.qAlpha(rgba)) |
| |
|
| | elif numcomponents <= 4: |
| | byteList.append(QtGui.qRed(rgba)) |
| | byteList.append(QtGui.qGreen(rgba)) |
| | byteList.append(QtGui.qBlue(rgba)) |
| |
|
| | if numcomponents == 4: |
| | byteList.append(QtGui.qAlpha(rgba)) |
| | |
| |
|
| | _bytes = bytes(byteList) |
| | img.setValue(size, numcomponents, _bytes) |
| | except FileNotFoundError as exc: |
| | _wrn("load_texture: {0}, {1}".format(exc.strerror, exc.filename)) |
| | return None |
| | except Exception as exc: |
| | _wrn(str(exc)) |
| | _wrn("load_texture: " + translate("draft", "unable to load texture")) |
| | return None |
| | else: |
| | return img |
| | return None |
| |
|
| |
|
| | loadTexture = load_texture |
| |
|
| |
|
| | def migrate_text_display_mode(obj_type="Text", mode="3D text", doc=None): |
| | """Migrate the display mode of objects of certain type.""" |
| | if not doc: |
| | doc = App.activeDocument() |
| |
|
| | for obj in doc.Objects: |
| | if utils.get_type(obj) == obj_type: |
| | obj.ViewObject.DisplayMode = mode |
| |
|
| |
|
| | def get_bbox(obj, debug=False): |
| | """Return a BoundBox from any object that has a Coin RootNode. |
| | |
| | Normally the bounding box of an object can be taken |
| | from its `Part::TopoShape`. |
| | :: |
| | >>> print(obj.Shape.BoundBox) |
| | |
| | However, for objects without a `Shape`, such as those |
| | derived from `App::FeaturePython` like `Draft Text` and `Draft Dimension`, |
| | the bounding box can be calculated from the `RootNode` of the viewprovider. |
| | |
| | Parameters |
| | ---------- |
| | obj: App::DocumentObject |
| | Any object that has a `ViewObject.RootNode`. |
| | |
| | Returns |
| | ------- |
| | Base::BoundBox |
| | It returns a `BoundBox` object which has information like |
| | minimum and maximum values of X, Y, and Z, as well as bounding box |
| | center. |
| | |
| | None |
| | If there is a problem it will return `None`. |
| | """ |
| | _name = "get_bbox" |
| |
|
| | found, doc = utils.find_doc(App.activeDocument()) |
| | if not found: |
| | _err(translate("draft", "No active document. Aborting.")) |
| | return None |
| |
|
| | if isinstance(obj, str): |
| | obj_str = obj |
| |
|
| | found, obj = utils.find_object(obj, doc) |
| | if not found: |
| | _err(translate("draft", "Wrong input: object {} not in document.").format(obj_str)) |
| | return None |
| |
|
| | if ( |
| | not hasattr(obj, "ViewObject") |
| | or not obj.ViewObject |
| | or not hasattr(obj.ViewObject, "RootNode") |
| | ): |
| | _err(translate("draft", "Does not have 'ViewObject.RootNode'.")) |
| |
|
| | |
| | |
| | node = obj.ViewObject.RootNode |
| |
|
| | view = Gui.ActiveDocument.ActiveView |
| | region = view.getViewer().getSoRenderManager().getViewportRegion() |
| | action = coin.SoGetBoundingBoxAction(region) |
| |
|
| | node.getBoundingBox(action) |
| | bb = action.getBoundingBox() |
| |
|
| | |
| | xmin, ymin, zmin = bb.getMin().getValue() |
| | xmax, ymax, zmax = bb.getMax().getValue() |
| |
|
| | return App.BoundBox(xmin, ymin, zmin, xmax, ymax, zmax) |
| |
|
| |
|
| | |
| | def find_coin_node(parent, nodetype): |
| | if not hasattr(parent, "getNumChildren"): |
| | return None |
| | for i in range(parent.getNumChildren()): |
| | if isinstance(parent.getChild(i), nodetype): |
| | return parent.getChild(i) |
| | return None |
| |
|
| |
|
| | def find_coin_node_by_name(parent, name): |
| | if not hasattr(parent, "getNumChildren"): |
| | return None |
| | for i in range(parent.getNumChildren()): |
| | if parent.getChild(i).getName() == name: |
| | return parent.getChild(i) |
| | return None |
| |
|
| |
|
| | |
| | |
| | |
| | def end_all_events(): |
| | view = get_3d_view() |
| | if view is None: |
| | return |
| | if view.getNavigationType() in ( |
| | "Gui::GestureNavigationStyle", |
| | "Gui::MayaGestureNavigationStyle", |
| | ): |
| | return |
| |
|
| | class DelayEnder: |
| | def __init__(self): |
| | self.delay_is_done = False |
| |
|
| | def stop(self): |
| | self.delay_is_done = True |
| |
|
| | ender = DelayEnder() |
| | timer = QtCore.QTimer() |
| | timer.timeout.connect(ender.stop) |
| | timer.setSingleShot(True) |
| | timer.start( |
| | 100 |
| | ) |
| | while not ender.delay_is_done: |
| | QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents) |
| |
|
| |
|
| | def toggle_working_plane(obj, action=None, restore=False, dialog=None): |
| | """Toggle the active state of a working plane object. |
| | |
| | This function handles the common logic for activating and deactivating |
| | working plane objects like BuildingParts and WorkingPlaneProxies. |
| | It can be used by different modules that need to implement similar |
| | working plane activation behavior. |
| | |
| | Parameters |
| | ---------- |
| | obj : App::DocumentObject |
| | The object to activate or deactivate as a working plane. |
| | action : QAction, optional |
| | The action button that triggered this function, to update its checked state. |
| | restore : bool, optional |
| | If True, will restore the previous working plane when deactivating. |
| | Defaults to False. |
| | dialog : QDialog, optional |
| | If provided, will update the checked state of the activate button in the dialog. |
| | |
| | Returns |
| | ------- |
| | bool |
| | True if the object was activated, False if it was deactivated. |
| | """ |
| |
|
| | |
| | context = "Arch" |
| | obj_type = utils.get_type(obj) |
| | if obj_type == "IfcBuildingStorey": |
| | context = "NativeIFC" |
| |
|
| | |
| | is_active_arch = Gui.ActiveDocument.ActiveView.getActiveObject("Arch") == obj |
| | is_active_ifc = Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") == obj |
| | is_active = is_active_arch or is_active_ifc |
| | if is_active: |
| | |
| | if is_active_arch: |
| | Gui.ActiveDocument.ActiveView.setActiveObject("Arch", None) |
| | if is_active_ifc: |
| | Gui.ActiveDocument.ActiveView.setActiveObject("NativeIFC", None) |
| |
|
| | if ( |
| | hasattr(obj, "ViewObject") |
| | and hasattr(obj.ViewObject, "Proxy") |
| | and hasattr(obj.ViewObject.Proxy, "setWorkingPlane") |
| | ): |
| | obj.ViewObject.Proxy.setWorkingPlane(restore=True) |
| | if action: |
| | action.setChecked(False) |
| | if dialog and hasattr(dialog, "buttonActive"): |
| | dialog.buttonActive.setChecked(False) |
| | return False |
| | else: |
| | |
| | Gui.ActiveDocument.ActiveView.setActiveObject(context, obj) |
| | if ( |
| | hasattr(obj, "ViewObject") |
| | and hasattr(obj.ViewObject, "Proxy") |
| | and hasattr(obj.ViewObject.Proxy, "setWorkingPlane") |
| | ): |
| | obj.ViewObject.Proxy.setWorkingPlane() |
| | if action: |
| | action.setChecked(True) |
| | if dialog and hasattr(dialog, "buttonActive"): |
| | dialog.buttonActive.setChecked(True) |
| | return True |
| |
|
| |
|
| | |
| |
|