| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import math |
| | import re |
| | import tempfile |
| | import time |
| | import uuid |
| |
|
| | import FreeCAD |
| | import ArchCommands |
| | import ArchComponent |
| | import Draft |
| | import DraftVecUtils |
| |
|
| | from FreeCAD import Vector |
| | from draftutils import params |
| |
|
| | if FreeCAD.GuiUp: |
| | from pivy import coin |
| | from PySide import QtCore, QtGui |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| | import FreeCADGui |
| | from draftutils.translate import translate |
| | else: |
| | |
| | def translate(ctxt, txt): |
| | return txt |
| |
|
| | def QT_TRANSLATE_NOOP(ctxt, txt): |
| | return txt |
| |
|
| | |
| |
|
| | ISRENDERING = False |
| |
|
| |
|
| | def getSectionData(source): |
| | """Returns some common data from section planes and building parts""" |
| |
|
| | if hasattr(source, "Objects"): |
| | objs = source.Objects |
| | cutplane = source.Shape |
| | elif hasattr(source, "Group"): |
| | import Part |
| |
|
| | objs = source.Group |
| | cutplane = Part.makePlane(1000, 1000, FreeCAD.Vector(-500, -500, 0)) |
| | m = 1 |
| | if source.ViewObject and hasattr(source.ViewObject, "CutMargin"): |
| | m = source.ViewObject.CutMargin.Value |
| | cutplane.translate(FreeCAD.Vector(0, 0, m)) |
| | cutplane.Placement = cutplane.Placement.multiply(source.Placement) |
| | onlySolids = True |
| | if hasattr(source, "OnlySolids"): |
| | onlySolids = source.OnlySolids |
| | clip = False |
| | if hasattr(source, "Clip"): |
| | clip = source.Clip |
| | p = FreeCAD.Placement(source.Placement) |
| | direction = p.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) |
| | if objs: |
| | objs = Draft.get_group_contents(objs, walls=True, addgroups=True) |
| | return objs, cutplane, onlySolids, clip, direction |
| |
|
| |
|
| | def getCutShapes( |
| | objs, cutplane, onlySolids, clip, joinArch, showHidden, groupSshapesByObject=False |
| | ): |
| | """ |
| | returns a list of shapes (visible, hidden, cut lines...) |
| | obtained from performing a series of booleans against the given cut plane |
| | """ |
| |
|
| | import Part |
| | import DraftGeomUtils |
| |
|
| | shapes = [] |
| | hshapes = [] |
| | sshapes = [] |
| | objectShapes = [] |
| | objectSshapes = [] |
| |
|
| | if joinArch: |
| | shtypes = {} |
| | for o in objs: |
| | if Draft.getType(o) in ["Wall", "Structure"]: |
| | if o.Shape.isNull(): |
| | pass |
| | elif onlySolids: |
| | shtypes.setdefault( |
| | o.Material.Name if (hasattr(o, "Material") and o.Material) else "None", [] |
| | ).extend(o.Shape.Solids) |
| | else: |
| | shtypes.setdefault( |
| | o.Material.Name if (hasattr(o, "Material") and o.Material) else "None", [] |
| | ).append(o.Shape.copy()) |
| | elif hasattr(o, "Shape"): |
| | if o.Shape.isNull(): |
| | pass |
| | elif onlySolids: |
| | shapes.extend(o.Shape.Solids) |
| | objectShapes.append((o, o.Shape.Solids)) |
| | else: |
| | shapes.append(o.Shape.copy()) |
| | objectShapes.append((o, [o.Shape.copy()])) |
| | for k, v in shtypes.items(): |
| | v1 = v.pop() |
| | if v: |
| | v1 = v1.multiFuse(v) |
| | v1 = v1.removeSplitter() |
| | if v1.Solids: |
| | shapes.extend(v1.Solids) |
| | objectShapes.append((k, v1.Solids)) |
| | else: |
| | print("ArchSectionPlane: Fusing Arch objects produced non-solid results") |
| | shapes.append(v1) |
| | objectShapes.append((k, [v1])) |
| | else: |
| | for o in objs: |
| | if hasattr(o, "Shape"): |
| | if o.Shape.isNull(): |
| | pass |
| | elif onlySolids: |
| | if o.Shape.isValid(): |
| | shapes.extend(o.Shape.Solids) |
| | objectShapes.append((o, o.Shape.Solids)) |
| | else: |
| | shapes.append(o.Shape) |
| | objectShapes.append((o, [o.Shape])) |
| |
|
| | cutface, cutvolume, invcutvolume = ArchCommands.getCutVolume(cutplane, shapes, clip) |
| | shapes = [] |
| | for o, shapeList in objectShapes: |
| | tmpSshapes = [] |
| | for sh in shapeList: |
| | for sub in (sh.SubShapes if sh.ShapeType == "Compound" else [sh]): |
| | if cutvolume: |
| | if sub.Volume < 0: |
| | sub = sub.reversed() |
| | c = sub.cut(cutvolume) |
| | s = sub.section(cutface) |
| | try: |
| | wires = DraftGeomUtils.findWires(s.Edges) |
| | for w in wires: |
| | f = Part.Face(w) |
| | tmpSshapes.append(f) |
| | except Part.OCCError: |
| | |
| | tmpSshapes.append(s) |
| | shapes.extend(c.SubShapes if c.ShapeType == "Compound" else [c]) |
| | if showHidden: |
| | c = sub.cut(invcutvolume) |
| | hshapes.extend(c.SubShapes if c.ShapeType == "Compound" else [c]) |
| | else: |
| | shapes.append(sub) |
| |
|
| | if len(tmpSshapes) > 0: |
| | sshapes.extend(tmpSshapes) |
| |
|
| | if groupSshapesByObject: |
| | objectSshapes.append((o, tmpSshapes)) |
| |
|
| | if groupSshapesByObject: |
| | return shapes, hshapes, sshapes, cutface, cutvolume, invcutvolume, objectSshapes |
| | else: |
| | return shapes, hshapes, sshapes, cutface, cutvolume, invcutvolume |
| |
|
| |
|
| | def getFillForObject(o, defaultFill, source): |
| | """returns a color tuple from an object's material""" |
| |
|
| | if hasattr(source, "UseMaterialColorForFill") and source.UseMaterialColorForFill: |
| | material = None |
| | if hasattr(o, "Material") and o.Material: |
| | material = o.Material |
| | elif isinstance(o, str): |
| | material = FreeCAD.ActiveDocument.getObject(o) |
| | if material: |
| | if hasattr(material, "SectionColor") and material.SectionColor: |
| | return material.SectionColor |
| | elif hasattr(material, "Color") and material.Color: |
| | return material.Color |
| | elif hasattr(o, "ViewObject") and hasattr(o.ViewObject, "ShapeColor"): |
| | return o.ViewObject.ShapeColor |
| | return defaultFill |
| |
|
| |
|
| | def isOriented(obj, plane): |
| | """determines if an annotation is facing the cutplane or not""" |
| |
|
| | norm1 = plane.normalAt(0, 0) |
| | if hasattr(obj, "Placement"): |
| | norm2 = obj.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) |
| | elif hasattr(obj, "Normal"): |
| | norm2 = obj.Normal |
| | if norm2.Length < 0.01: |
| | return True |
| | else: |
| | return True |
| | a = norm1.getAngle(norm2) |
| | if (a < 0.01) or (abs(a - math.pi) < 0.01): |
| | return True |
| | return False |
| |
|
| |
|
| | def update_svg_cache(source, renderMode, showHidden, showFill, fillSpaces, joinArch, allOn, objs): |
| | """ |
| | Returns None or cached SVG, clears shape cache if required |
| | """ |
| | svgcache = None |
| | if hasattr(source, "Proxy"): |
| | if hasattr(source.Proxy, "svgcache") and source.Proxy.svgcache: |
| | |
| | svgcache = source.Proxy.svgcache[0] |
| | |
| | if ( |
| | source.Proxy.svgcache[1] != renderMode |
| | or source.Proxy.svgcache[2] != showHidden |
| | or source.Proxy.svgcache[3] != showFill |
| | or source.Proxy.svgcache[4] != fillSpaces |
| | or source.Proxy.svgcache[5] != joinArch |
| | or source.Proxy.svgcache[6] != allOn |
| | or source.Proxy.svgcache[7] != set(objs) |
| | ): |
| | svgcache = None |
| | if ( |
| | source.Proxy.svgcache[4] != fillSpaces |
| | or source.Proxy.svgcache[5] != joinArch |
| | or source.Proxy.svgcache[6] != allOn |
| | or source.Proxy.svgcache[7] != set(objs) |
| | ): |
| | source.Proxy.shapecache = None |
| | return svgcache |
| |
|
| |
|
| | def getSVG( |
| | source, |
| | renderMode="Wireframe", |
| | allOn=False, |
| | showHidden=False, |
| | scale=1, |
| | rotation=0, |
| | linewidth=1, |
| | lineColor=(0.0, 0.0, 0.0), |
| | fontsize=1, |
| | linespacing=None, |
| | showFill=False, |
| | fillColor=(1.0, 1.0, 1.0), |
| | techdraw=False, |
| | fillSpaces=False, |
| | cutlinewidth=0, |
| | joinArch=False, |
| | ): |
| | """ |
| | Return an SVG fragment from an Arch SectionPlane or BuildingPart. |
| | |
| | allOn |
| | If it is `True`, all cut objects are shown, regardless of if they are |
| | visible or not. |
| | |
| | renderMode |
| | Can be `'Wireframe'` (default) or `'Solid'` to use the Arch solid |
| | renderer. |
| | |
| | showHidden |
| | If it is `True`, the hidden geometry above the section plane |
| | is shown in dashed line. |
| | |
| | showFill |
| | If it is `True`, the cut areas get filled with a pattern. |
| | |
| | lineColor |
| | Color of lines for the `renderMode` is `'Wireframe'`. |
| | |
| | fillColor |
| | If `showFill` is `True` and `renderMode` is `'Wireframe'`, |
| | the cut areas are filled with `fillColor`. |
| | |
| | fillSpaces |
| | If `True`, shows space objects as filled surfaces. |
| | """ |
| | import Part |
| |
|
| | objs, cutplane, onlySolids, clip, direction = getSectionData(source) |
| | if not objs: |
| | return "" |
| | if not allOn: |
| | objs = Draft.removeHidden(objs) |
| |
|
| | |
| | spaces = [] |
| | nonspaces = [] |
| | drafts = [] |
| | windows = [] |
| | cutface = None |
| | for o in objs: |
| | if Draft.getType(o) == "Space": |
| | spaces.append(o) |
| | elif Draft.getType(o) in [ |
| | "Dimension", |
| | "AngularDimension", |
| | "LinearDimension", |
| | "Annotation", |
| | "Label", |
| | "Text", |
| | "DraftText", |
| | "Axis", |
| | ]: |
| | if isOriented(o, cutplane): |
| | drafts.append(o) |
| | elif o.isDerivedFrom("App::DocumentObjectGroup"): |
| | |
| | pass |
| | else: |
| | nonspaces.append(o) |
| | if Draft.getType(o.getLinkedObject()) == "Window": |
| | windows.append(o) |
| | objs = nonspaces |
| |
|
| | scaledLineWidth = linewidth / scale |
| | if renderMode in ["Coin", 2, "Coin mono", 3]: |
| | |
| | svgLineWidth = str(linewidth) + "px" |
| | else: |
| | svgLineWidth = str(scaledLineWidth) + "px" |
| | if cutlinewidth: |
| | scaledCutLineWidth = cutlinewidth / scale |
| | svgCutLineWidth = str(scaledCutLineWidth) + "px" |
| | else: |
| | st = params.get_param_arch("CutLineThickness") |
| | svgCutLineWidth = str(scaledLineWidth * st) + "px" |
| | yt = params.get_param_arch("SymbolLineThickness") |
| | svgSymbolLineWidth = str(linewidth * yt) |
| | hiddenPattern = params.get_param_arch("archHiddenPattern") |
| | svgHiddenPattern = hiddenPattern.replace(" ", "") |
| | |
| | |
| | |
| | |
| | svgLineColor = Draft.getrgb(lineColor) |
| | svg = "" |
| | |
| | svgcache = update_svg_cache( |
| | source, renderMode, showHidden, showFill, fillSpaces, joinArch, allOn, objs |
| | ) |
| | should_update_svg_cache = False |
| | if showFill or not svgcache: |
| | should_update_svg_cache = True |
| |
|
| | |
| | if renderMode in ["Coin", 2, "Coin mono", 3]: |
| | |
| | if hasattr(source.ViewObject, "ViewData") and source.ViewObject.ViewData: |
| | cameradata = None |
| | else: |
| | cameradata = None |
| | if should_update_svg_cache: |
| | if renderMode in ["Coin mono", 3]: |
| | svgcache = getCoinSVG( |
| | cutplane, objs, cameradata, linewidth="SVGLINEWIDTH", facecolor="#ffffff" |
| | ) |
| | else: |
| | svgcache = getCoinSVG(cutplane, objs, cameradata, linewidth="SVGLINEWIDTH") |
| | elif renderMode in ["Solid", 1]: |
| | if should_update_svg_cache: |
| | svgcache = "" |
| | |
| | import ArchVRM |
| | import WorkingPlane |
| |
|
| | wp = WorkingPlane.PlaneBase() |
| | pl = FreeCAD.Placement(source.Placement) |
| | if source.ViewObject and hasattr(source.ViewObject, "CutMargin"): |
| | mv = pl.multVec(FreeCAD.Vector(0, 0, 1)) |
| | mv.multiply(source.ViewObject.CutMargin) |
| | pl.move(mv) |
| | wp.align_to_placement(pl) |
| | |
| | render = ArchVRM.Renderer() |
| | render.setWorkingPlane(wp) |
| | render.addObjects(objs) |
| | if showHidden: |
| | render.cut(cutplane, showHidden) |
| | else: |
| | render.cut(cutplane) |
| | g = '<g transform="scale(1,-1)">\n' |
| | if hasattr(source.ViewObject, "RotateSolidRender"): |
| | if source.ViewObject.RotateSolidRender.Value != 0: |
| | g = '<g transform="scale(1,-1) rotate(' |
| | g += str(source.ViewObject.RotateSolidRender.Value) |
| | g += ')">\n' |
| | svgcache += g |
| | svgcache += render.getViewSVG(linewidth="SVGLINEWIDTH") |
| | |
| | svgcache += render.getSectionSVG(linewidth="SVGCUTLINEWIDTH", fillpattern="#ffffff") |
| | if showHidden: |
| | svgcache += render.getHiddenSVG(linewidth="SVGLINEWIDTH") |
| | svgcache += "</g>\n" |
| | |
| | else: |
| | |
| |
|
| | if ( |
| | hasattr(source, "Proxy") |
| | and hasattr(source.Proxy, "shapecache") |
| | and source.Proxy.shapecache |
| | ): |
| | vshapes = source.Proxy.shapecache[0] |
| | hshapes = source.Proxy.shapecache[1] |
| | sshapes = source.Proxy.shapecache[2] |
| | cutface = source.Proxy.shapecache[3] |
| | |
| | |
| | objectSshapes = source.Proxy.shapecache[6] |
| | else: |
| | if showFill: |
| | vshapes, hshapes, sshapes, cutface, cutvolume, invcutvolume, objectSshapes = ( |
| | getCutShapes(objs, cutplane, onlySolids, clip, joinArch, showHidden, True) |
| | ) |
| | else: |
| | vshapes, hshapes, sshapes, cutface, cutvolume, invcutvolume = getCutShapes( |
| | objs, cutplane, onlySolids, clip, joinArch, showHidden |
| | ) |
| | objectSshapes = [] |
| | source.Proxy.shapecache = [ |
| | vshapes, |
| | hshapes, |
| | sshapes, |
| | cutface, |
| | cutvolume, |
| | invcutvolume, |
| | objectSshapes, |
| | ] |
| |
|
| | if should_update_svg_cache: |
| | svgcache = "" |
| | |
| | import TechDraw |
| | import Part |
| |
|
| | if vshapes: |
| | baseshape = Part.makeCompound(vshapes) |
| | style = {"stroke": "SVGLINECOLOR", "stroke-width": "SVGLINEWIDTH"} |
| | svgcache += TechDraw.projectToSVG( |
| | baseshape, |
| | direction, |
| | hStyle=style, |
| | h0Style=style, |
| | h1Style=style, |
| | vStyle=style, |
| | v0Style=style, |
| | v1Style=style, |
| | ) |
| | if hshapes: |
| | hshapes = Part.makeCompound(hshapes) |
| | style = { |
| | "stroke": "SVGLINECOLOR", |
| | "stroke-width": "SVGLINEWIDTH", |
| | "stroke-dasharray": "SVGHIDDENPATTERN", |
| | } |
| | svgcache += TechDraw.projectToSVG( |
| | hshapes, |
| | direction, |
| | hStyle=style, |
| | h0Style=style, |
| | h1Style=style, |
| | vStyle=style, |
| | v0Style=style, |
| | v1Style=style, |
| | ) |
| | if sshapes: |
| | if showFill: |
| | |
| | svgcache += '<g transform="rotate(180)">\n' |
| | for o, shapes in objectSshapes: |
| | for s in shapes: |
| | if s.Edges: |
| | objectFill = getFillForObject(o, fillColor, source) |
| | |
| | |
| | |
| | |
| | |
| | |
| | svgcache += Draft.get_svg( |
| | s, |
| | linewidth=0, |
| | fillstyle=Draft.getrgb(objectFill, testbw=False), |
| | direction=direction.negative(), |
| | color=lineColor, |
| | ) |
| | svgcache += "</g>\n" |
| | sshapes = Part.makeCompound(sshapes) |
| | style = {"stroke": "SVGLINECOLOR", "stroke-width": "SVGCUTLINEWIDTH"} |
| | svgcache += TechDraw.projectToSVG( |
| | sshapes, |
| | direction, |
| | hStyle=style, |
| | h0Style=style, |
| | h1Style=style, |
| | vStyle=style, |
| | v0Style=style, |
| | v1Style=style, |
| | ) |
| | if should_update_svg_cache: |
| | if hasattr(source, "Proxy"): |
| | source.Proxy.svgcache = [ |
| | svgcache, |
| | renderMode, |
| | showHidden, |
| | showFill, |
| | fillSpaces, |
| | joinArch, |
| | allOn, |
| | set(objs), |
| | ] |
| |
|
| | svgcache = svgcache.replace("SVGLINECOLOR", svgLineColor) |
| | svgcache = svgcache.replace("SVGLINEWIDTH", svgLineWidth) |
| | svgcache = svgcache.replace("SVGHIDDENPATTERN", svgHiddenPattern) |
| | svgcache = svgcache.replace("SVGCUTLINEWIDTH", svgCutLineWidth) |
| | svg += svgcache |
| |
|
| | if drafts: |
| | if not techdraw: |
| | svg += '<g transform="scale(1,-1)">' |
| | for d in drafts: |
| | svg += Draft.get_svg( |
| | d, |
| | scale=scale, |
| | linewidth=svgSymbolLineWidth, |
| | fontsize=fontsize, |
| | linespacing=linespacing, |
| | direction=direction, |
| | color=lineColor, |
| | techdraw=techdraw, |
| | rotation=rotation, |
| | override=False, |
| | ) |
| | if not techdraw: |
| | svg += "</g>" |
| |
|
| | if not cutface: |
| | |
| | cutface = cutplane |
| |
|
| | |
| | if cutface and spaces: |
| | spaces = [s for s in spaces if s.Shape.BoundBox.intersect(cutface.BoundBox)] |
| | if spaces: |
| | if not techdraw: |
| | svg += '<g transform="scale(1,-1)">' |
| | for s in spaces: |
| | svg += Draft.get_svg( |
| | s, |
| | scale=scale, |
| | linewidth=svgSymbolLineWidth, |
| | fontsize=fontsize, |
| | linespacing=linespacing, |
| | direction=direction, |
| | color=lineColor, |
| | techdraw=techdraw, |
| | rotation=rotation, |
| | fillspaces=fillSpaces, |
| | ) |
| | if not techdraw: |
| | svg += "</g>" |
| |
|
| | |
| | cutwindows = [] |
| | if cutface and windows and BoundBoxValid(cutface.BoundBox): |
| | cutwindows = [ |
| | w.Name |
| | for w in windows |
| | if BoundBoxValid(w.Shape.BoundBox) and w.Shape.BoundBox.intersect(cutface.BoundBox) |
| | ] |
| | if windows: |
| | sh = [] |
| | for w in windows: |
| | if w.Name in cutwindows: |
| | wlo = w.getLinkedObject() |
| | if hasattr(wlo, "SymbolPlan") and wlo.SymbolPlan: |
| | if not hasattr(wlo.Proxy, "sshapes"): |
| | wlo.Proxy.execute(wlo) |
| | if hasattr(wlo.Proxy, "sshapes") and wlo.Proxy.sshapes: |
| | c = Part.makeCompound(wlo.Proxy.sshapes) |
| | c.Placement = w.Placement |
| | sh.append(c) |
| | if sh: |
| | if not techdraw: |
| | svg += '<g transform="scale(1,-1)">' |
| | for s in sh: |
| | svg += Draft.get_svg( |
| | s, |
| | scale=scale, |
| | linewidth=svgSymbolLineWidth, |
| | fontsize=fontsize, |
| | linespacing=linespacing, |
| | fillstyle="none", |
| | direction=direction, |
| | color=lineColor, |
| | techdraw=techdraw, |
| | rotation=rotation, |
| | ) |
| | if not techdraw: |
| | svg += "</g>" |
| |
|
| | return svg |
| |
|
| |
|
| | def BoundBoxValid(boundBox) -> bool: |
| | """Return true if boundBox has a non-zero volume""" |
| | return boundBox.XLength > 0 and boundBox.YLength > 0 and boundBox.ZLength > 0 |
| |
|
| |
|
| | def getDXF(obj): |
| | """Return a DXF representation from a TechDraw view.""" |
| | allOn = getattr(obj, "AllOn", True) |
| | showHidden = getattr(obj, "ShowHidden", False) |
| | result = [] |
| | import TechDraw |
| | import Part |
| |
|
| | if not obj.Source: |
| | return result |
| | source = obj.Source |
| | objs, cutplane, onlySolids, clip, direction = getSectionData(source) |
| | if not objs: |
| | return result |
| | if not allOn: |
| | objs = Draft.removeHidden(objs) |
| | objs = [ |
| | obj |
| | for obj in objs |
| | if ( |
| | not obj.isDerivedFrom("Part::Part2DObject") |
| | and Draft.getType(obj) |
| | not in ["BezCurve", "BSpline", "Wire", "Annotation", "Dimension", "Space"] |
| | ) |
| | ] |
| | vshapes, hshapes, sshapes, cutface, cutvolume, invcutvolume = getCutShapes( |
| | objs, cutplane, onlySolids, clip, False, showHidden |
| | ) |
| | if vshapes: |
| | result.append(TechDraw.projectToDXF(Part.makeCompound(vshapes), direction)) |
| | if sshapes: |
| | result.append(TechDraw.projectToDXF(Part.makeCompound(sshapes), direction)) |
| | if hshapes: |
| | result.append(TechDraw.projectToDXF(Part.makeCompound(hshapes), direction)) |
| | return result |
| |
|
| |
|
| | def getCameraData(floatlist): |
| | """reconstructs a valid camera data string from stored values""" |
| |
|
| | c = "" |
| | if len(floatlist) >= 12: |
| | d = floatlist |
| | camtype = "orthographic" |
| | if len(floatlist) == 13: |
| | if d[12] == 1: |
| | camtype = "perspective" |
| | if camtype == "orthographic": |
| | c = "#Inventor V2.1 ascii\n\n\nOrthographicCamera {\n viewportMapping ADJUST_CAMERA\n " |
| | else: |
| | c = "#Inventor V2.1 ascii\n\n\nPerspectiveCamera {\n viewportMapping ADJUST_CAMERA\n " |
| | c += "position " + str(d[0]) + " " + str(d[1]) + " " + str(d[2]) + "\n " |
| | c += ( |
| | "orientation " |
| | + str(d[3]) |
| | + " " |
| | + str(d[4]) |
| | + " " |
| | + str(d[5]) |
| | + " " |
| | + str(d[6]) |
| | + "\n " |
| | ) |
| | c += "aspectRatio " + str(d[9]) + "\n " |
| | c += "focalDistance " + str(d[10]) + "\n " |
| | if camtype == "orthographic": |
| | c += "height " + str(d[11]) + "\n\n}\n" |
| | else: |
| | c += "heightAngle " + str(d[11]) + "\n\n}\n" |
| | return c |
| |
|
| |
|
| | def getCoinSVG(cutplane, objs, cameradata=None, linewidth=0.2, singleface=False, facecolor=None): |
| | """Returns an SVG fragment generated from a coin view""" |
| |
|
| | if not FreeCAD.GuiUp: |
| | return "" |
| |
|
| | |
| | |
| | global ISRENDERING |
| | while ISRENDERING: |
| | time.sleep(0.1) |
| |
|
| | ISRENDERING = True |
| |
|
| | |
| | svgfile = tempfile.mkstemp(suffix=".svg")[1] |
| |
|
| | |
| | |
| | ldict = {} |
| | if singleface: |
| | for obj in objs: |
| | if hasattr(obj, "ViewObject") and hasattr(obj.ViewObject, "Lighting"): |
| | ldict[obj.Name] = obj.ViewObject.Lighting |
| | obj.ViewObject.Lighting = "One side" |
| |
|
| | |
| | root_node = coin.SoSeparator() |
| | boundbox = FreeCAD.BoundBox() |
| | for obj in objs: |
| | if hasattr(obj.ViewObject, "RootNode") and obj.ViewObject.RootNode: |
| | old_visibility = obj.ViewObject.isVisible() |
| | |
| | obj.ViewObject.show() |
| | node_copy = obj.ViewObject.RootNode.copy() |
| | root_node.addChild(node_copy) |
| | if old_visibility: |
| | obj.ViewObject.show() |
| | else: |
| | obj.ViewObject.hide() |
| |
|
| | if hasattr(obj, "Shape") and hasattr(obj.Shape, "BoundBox"): |
| | boundbox.add(obj.Shape.BoundBox) |
| |
|
| | |
| | if ldict: |
| | for obj in objs: |
| | if obj.Name in ldict: |
| | obj.ViewObject.Lighting = ldict[obj.Name] |
| |
|
| | |
| | view_window = FreeCADGui.createViewer() |
| | view_window_name = "Temp" + str(uuid.uuid4().hex[:8]) |
| | view_window.setName(view_window_name) |
| | |
| | |
| | view_window.setAnimationEnabled(False) |
| | inventor_view = view_window.getViewer() |
| |
|
| | inventor_view.setBackgroundColor(1, 1, 1) |
| | view_window.redraw() |
| |
|
| | |
| | clip = coin.SoClipPlane() |
| | norm = cutplane.normalAt(0, 0).negative() |
| | proj = DraftVecUtils.project(cutplane.CenterOfMass, norm) |
| | dist = proj.Length |
| | if proj.getAngle(norm) > 1: |
| | dist = -dist |
| | clip.on = True |
| | plane = coin.SbPlane(coin.SbVec3f(norm.x, norm.y, norm.z), dist) |
| | clip.plane.setValue(plane) |
| | root_node.insertChild(clip, 0) |
| |
|
| | |
| | markervec = FreeCAD.Vector(10, 10, 10) |
| | a = cutplane.normalAt(0, 0).getAngle(markervec) |
| | if (a < 0.01) or (abs(a - math.pi) < 0.01): |
| | markervec = FreeCAD.Vector(10, -10, 10) |
| | boundbox.enlarge(10) |
| | sep = coin.SoSeparator() |
| | mat = coin.SoMaterial() |
| | mat.diffuseColor.setValue([1, 1, 1]) |
| | sep.addChild(mat) |
| | coords = coin.SoCoordinate3() |
| | coords.point.setValues( |
| | [ |
| | [boundbox.XMin, boundbox.YMin, boundbox.ZMin], |
| | [boundbox.XMin + markervec.x, boundbox.YMin + markervec.y, boundbox.ZMin + markervec.z], |
| | ] |
| | ) |
| | sep.addChild(coords) |
| | lset = coin.SoIndexedLineSet() |
| | lset.coordIndex.setValues(0, 2, [0, 1]) |
| | sep.addChild(lset) |
| | root_node.insertChild(sep, 0) |
| |
|
| | |
| | inventor_view.setSceneGraph(root_node) |
| |
|
| | |
| | if cameradata: |
| | view_window.setCamera(cameradata) |
| | else: |
| | view_window.setCameraType("Orthographic") |
| | |
| | vx = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0)) |
| | vy = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(0, 1, 0)) |
| | vz = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) |
| | rot = FreeCAD.Rotation(vx, vy, vz, "ZXY") |
| | view_window.setCameraOrientation(rot.Q) |
| | |
| | view_window.fitAll() |
| |
|
| | |
| | |
| | view_window.saveVectorGraphic(svgfile, 1) |
| |
|
| | |
| | f = open(svgfile, "r") |
| | svg = f.read() |
| | f.close() |
| | svg = svg.replace("stroke-width:1.0;", "stroke-width:" + str(linewidth) + ";") |
| | svg = svg.replace('stroke-width="1px', 'stroke-width="' + str(linewidth)) |
| |
|
| | |
| | |
| | factor = None |
| | trans = None |
| | import WorkingPlane |
| |
|
| | wp = WorkingPlane.PlaneBase() |
| | wp.align_to_point_and_axis_svg(Vector(0, 0, 0), cutplane.normalAt(0, 0), 0) |
| | p = wp.get_local_coords(markervec) |
| | orlength = FreeCAD.Vector(p.x, p.y, 0).Length |
| | marker = re.findall(r"<line x1=.*?stroke=\"\#ffffff\".*?\/>", svg) |
| | if marker: |
| | marker = marker[0].split('"') |
| | x1 = float(marker[1]) |
| | y1 = float(marker[3]) |
| | x2 = float(marker[5]) |
| | y2 = float(marker[7]) |
| | p1 = FreeCAD.Vector(x1, y1, 0) |
| | p2 = FreeCAD.Vector(x2, y2, 0) |
| | factor = orlength / p2.sub(p1).Length |
| | if factor: |
| | orig = wp.get_local_coords(FreeCAD.Vector(boundbox.XMin, boundbox.YMin, boundbox.ZMin)) |
| | orig = FreeCAD.Vector(orig.x, -orig.y, 0) |
| | scaledp1 = FreeCAD.Vector(p1.x * factor, p1.y * factor, 0) |
| | trans = orig.sub(scaledp1) |
| | |
| | svg = re.sub(r"<line x1=.*?stroke=\"\#ffffff\".*?\/>", "", svg, count=1) |
| |
|
| | |
| | svg = re.sub(r"<path.*?>", "", svg, count=1, flags=re.MULTILINE | re.DOTALL) |
| |
|
| | |
| | if facecolor: |
| | res = re.findall(r"fill:(.*?); stroke:(.*?);", svg) |
| | pairs = [] |
| | for pair in res: |
| | if (pair not in pairs) and (pair[0] == pair[1]) and (pair[0] not in ["#0a0a0a"]): |
| | |
| | pairs.append(pair) |
| | for pair in pairs: |
| | svg = re.sub( |
| | r"fill:" + pair[0] + "; stroke:" + pair[1] + ";", |
| | "fill:" + facecolor + "; stroke:" + facecolor + ";", |
| | svg, |
| | ) |
| |
|
| | |
| | if factor: |
| | if trans: |
| | svg = svg.replace( |
| | "<g>", |
| | '<g transform="translate(' |
| | + str(trans.x) |
| | + " " |
| | + str(trans.y) |
| | + ") scale(" |
| | + str(factor) |
| | + "," |
| | + str(factor) |
| | + ')">\n<g>', |
| | 1, |
| | ) |
| | else: |
| | svg = svg.replace( |
| | "<g>", '<g transform="scale(' + str(factor) + "," + str(factor) + ')">\n<g>', 1 |
| | ) |
| | svg = svg.replace("</svg>", "</g>\n</svg>") |
| |
|
| | |
| | QtCore.QTimer.singleShot(1, lambda: closeViewer(view_window_name)) |
| |
|
| | |
| | svg = re.sub(r"<\?xml.*?>", "", svg, flags=re.MULTILINE | re.DOTALL) |
| | svg = re.sub(r"<svg.*?>", "", svg, flags=re.MULTILINE | re.DOTALL) |
| | svg = re.sub(r"<\/svg>", "", svg, flags=re.MULTILINE | re.DOTALL) |
| |
|
| | ISRENDERING = False |
| |
|
| | return svg |
| |
|
| |
|
| | def closeViewer(name): |
| | """Close temporary viewers""" |
| |
|
| | mw = FreeCADGui.getMainWindow() |
| | for sw in mw.findChildren(QtGui.QMdiSubWindow): |
| | if sw.windowTitle() == name: |
| | sw.close() |
| |
|
| |
|
| | class _SectionPlane: |
| | "A section plane object" |
| |
|
| | def __init__(self, obj): |
| | obj.Proxy = self |
| | self.Type = "SectionPlane" |
| | self.setProperties(obj) |
| |
|
| | def setProperties(self, obj): |
| |
|
| | pl = obj.PropertiesList |
| | if not "Placement" in pl: |
| | obj.addProperty( |
| | "App::PropertyPlacement", |
| | "Placement", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The placement of this object"), |
| | locked=True, |
| | ) |
| | if not "Shape" in pl: |
| | obj.addProperty( |
| | "Part::PropertyPartShape", |
| | "Shape", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The shape of this object"), |
| | locked=True, |
| | ) |
| | if not "Objects" in pl: |
| | obj.addProperty( |
| | "App::PropertyLinkList", |
| | "Objects", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The objects that must be considered by this section plane. Empty means the whole document.", |
| | ), |
| | locked=True, |
| | ) |
| | if not "OnlySolids" in pl: |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "OnlySolids", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If false, non-solids will be cut too, with possible wrong results.", |
| | ), |
| | locked=True, |
| | ) |
| | obj.OnlySolids = True |
| | if not "Clip" in pl: |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "Clip", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If True, resulting views will be clipped to the section plane area.", |
| | ), |
| | locked=True, |
| | ) |
| | if not "UseMaterialColorForFill" in pl: |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "UseMaterialColorForFill", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If true, the color of the objects material will be used to fill cut areas.", |
| | ), |
| | locked=True, |
| | ) |
| | obj.UseMaterialColorForFill = False |
| | if not "Depth" in pl: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "Depth", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Geometry further than this value will be cut off. Keep zero for unlimited.", |
| | ), |
| | locked=True, |
| | ) |
| |
|
| | def onDocumentRestored(self, obj): |
| |
|
| | self.setProperties(obj) |
| |
|
| | def execute(self, obj): |
| | import math |
| | import Part |
| |
|
| | l = 1 |
| | h = 1 |
| | if obj.ViewObject: |
| | if hasattr(obj.ViewObject, "DisplayLength"): |
| | l = obj.ViewObject.DisplayLength.Value |
| | h = obj.ViewObject.DisplayHeight.Value |
| | elif hasattr(obj.ViewObject, "DisplaySize"): |
| | |
| | l = obj.ViewObject.DisplaySize.Value |
| | h = obj.ViewObject.DisplaySize.Value |
| | if not l: |
| | l = 1 |
| | if not h: |
| | h = 1 |
| | p = Part.makePlane(l, h, Vector(l / 2, -h / 2, 0), Vector(0, 0, -1)) |
| | |
| | |
| | p.Placement = obj.Placement |
| |
|
| | |
| | |
| | target_normal = obj.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) |
| | if p.normalAt(0, 0).getAngle(target_normal) > math.pi / 2: |
| | p.reverse() |
| | obj.Shape = p |
| | self.svgcache = None |
| | self.shapecache = None |
| |
|
| | def getNormal(self, obj): |
| |
|
| | return obj.Shape.Faces[0].normalAt(0, 0) |
| |
|
| | def dumps(self): |
| |
|
| | return None |
| |
|
| | def loads(self, state): |
| |
|
| | self.Type = "SectionPlane" |
| |
|
| |
|
| | class _ViewProviderSectionPlane: |
| | "A View Provider for Section Planes" |
| |
|
| | def __init__(self, vobj): |
| |
|
| | vobj.Proxy = self |
| | self.setProperties(vobj) |
| |
|
| | def setProperties(self, vobj): |
| |
|
| | pl = vobj.PropertiesList |
| | d = 0 |
| | if "DisplaySize" in pl: |
| | d = vobj.DisplaySize.Value |
| | vobj.removeProperty("DisplaySize") |
| | if not "DisplayLength" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "DisplayLength", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The display length of this section plane"), |
| | locked=True, |
| | ) |
| | if d: |
| | vobj.DisplayLength = d |
| | else: |
| | vobj.DisplayLength = 1000 |
| | if not "DisplayHeight" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "DisplayHeight", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The display height of this section plane"), |
| | locked=True, |
| | ) |
| | if d: |
| | vobj.DisplayHeight = d |
| | else: |
| | vobj.DisplayHeight = 1000 |
| | if not "ArrowSize" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "ArrowSize", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The size of the arrows of this section plane"), |
| | locked=True, |
| | ) |
| | vobj.ArrowSize = 50 |
| | if not "Transparency" in pl: |
| | vobj.addProperty( |
| | "App::PropertyPercent", |
| | "Transparency", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The transparency of this object"), |
| | locked=True, |
| | ) |
| | vobj.Transparency = 85 |
| | if not "LineWidth" in pl: |
| | vobj.addProperty( |
| | "App::PropertyFloat", |
| | "LineWidth", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The line width of this object"), |
| | locked=True, |
| | ) |
| | vobj.LineWidth = 1 |
| | if not "CutDistance" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "CutDistance", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "Show the cut in the 3D view"), |
| | locked=True, |
| | ) |
| | if not "LineColor" in pl: |
| | vobj.addProperty( |
| | "App::PropertyColor", |
| | "LineColor", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The color of this object"), |
| | locked=True, |
| | ) |
| | vobj.LineColor = ArchCommands.getDefaultColor("Helpers") |
| | if not "CutView" in pl: |
| | vobj.addProperty( |
| | "App::PropertyBool", |
| | "CutView", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "Show the cut in the 3D view"), |
| | locked=True, |
| | ) |
| | if not "CutMargin" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "CutMargin", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The distance between the cut plane and the actual view cut (keep this a very small value but not zero)", |
| | ), |
| | locked=True, |
| | ) |
| | vobj.CutMargin = 1 |
| | if not "ShowLabel" in pl: |
| | vobj.addProperty( |
| | "App::PropertyBool", |
| | "ShowLabel", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "Show the label in the 3D view"), |
| | locked=True, |
| | ) |
| | if not "FontName" in pl: |
| | vobj.addProperty( |
| | "App::PropertyFont", |
| | "FontName", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The name of the font"), |
| | locked=True, |
| | ) |
| | vobj.FontName = params.get_param("textfont") |
| | if not "FontSize" in pl: |
| | vobj.addProperty( |
| | "App::PropertyLength", |
| | "FontSize", |
| | "SectionPlane", |
| | QT_TRANSLATE_NOOP("App::Property", "The size of the text font"), |
| | locked=True, |
| | ) |
| | vobj.FontSize = params.get_param("textheight") * params.get_param( |
| | "DefaultAnnoScaleMultiplier" |
| | ) |
| |
|
| | def onDocumentRestored(self, vobj): |
| |
|
| | self.setProperties(vobj) |
| |
|
| | def getIcon(self): |
| |
|
| | import Arch_rc |
| |
|
| | return ":/icons/Arch_SectionPlane_Tree.svg" |
| |
|
| | def claimChildren(self): |
| | |
| | |
| | |
| | return [] |
| |
|
| | def attach(self, vobj): |
| |
|
| | self.Object = vobj.Object |
| | self.clip = None |
| | self.mat1 = coin.SoMaterial() |
| | self.mat2 = coin.SoMaterial() |
| | self.fcoords = coin.SoCoordinate3() |
| | |
| | fs = coin.SoIndexedFaceSet() |
| | fs.coordIndex.setValues(0, 7, [0, 1, 2, -1, 0, 2, 3]) |
| | self.drawstyle = coin.SoDrawStyle() |
| | self.drawstyle.style = coin.SoDrawStyle.LINES |
| | self.lcoords = coin.SoCoordinate3() |
| | import PartGui |
| |
|
| | ls = coin.SoType.fromName("SoBrepEdgeSet").createInstance() |
| | ls.coordIndex.setValues( |
| | 0, |
| | 57, |
| | [ |
| | 0, |
| | 1, |
| | -1, |
| | 2, |
| | 3, |
| | 4, |
| | 5, |
| | -1, |
| | 6, |
| | 7, |
| | 8, |
| | 9, |
| | -1, |
| | 10, |
| | 11, |
| | -1, |
| | 12, |
| | 13, |
| | 14, |
| | 15, |
| | -1, |
| | 16, |
| | 17, |
| | 18, |
| | 19, |
| | -1, |
| | 20, |
| | 21, |
| | -1, |
| | 22, |
| | 23, |
| | 24, |
| | 25, |
| | -1, |
| | 26, |
| | 27, |
| | 28, |
| | 29, |
| | -1, |
| | 30, |
| | 31, |
| | -1, |
| | 32, |
| | 33, |
| | 34, |
| | 35, |
| | -1, |
| | 36, |
| | 37, |
| | 38, |
| | 39, |
| | -1, |
| | 40, |
| | 41, |
| | 42, |
| | 43, |
| | 44, |
| | ], |
| | ) |
| | self.txtcoords = coin.SoTransform() |
| | self.txtfont = coin.SoFont() |
| | self.txtfont.name = "" |
| | self.txt = coin.SoAsciiText() |
| | self.txt.justification = coin.SoText2.LEFT |
| | self.txt.string.setValue(" ") |
| | sep = coin.SoSeparator() |
| | psep = coin.SoSeparator() |
| | fsep = coin.SoSeparator() |
| | tsep = coin.SoSeparator() |
| | fsep.addChild(self.mat2) |
| | fsep.addChild(self.fcoords) |
| | fsep.addChild(fs) |
| | psep.addChild(self.mat1) |
| | psep.addChild(self.drawstyle) |
| | psep.addChild(self.lcoords) |
| | psep.addChild(ls) |
| | tsep.addChild(self.mat1) |
| | tsep.addChild(self.txtcoords) |
| | tsep.addChild(self.txtfont) |
| | tsep.addChild(self.txt) |
| | sep.addChild(fsep) |
| | sep.addChild(psep) |
| | sep.addChild(tsep) |
| | vobj.addDisplayMode(sep, "Default") |
| | self.onChanged(vobj, "DisplayLength") |
| | self.onChanged(vobj, "LineColor") |
| | self.onChanged(vobj, "Transparency") |
| | self.onChanged(vobj, "CutView") |
| |
|
| | def getDisplayModes(self, vobj): |
| |
|
| | return ["Default"] |
| |
|
| | def getDefaultDisplayMode(self): |
| |
|
| | return "Default" |
| |
|
| | def setDisplayMode(self, mode): |
| |
|
| | return mode |
| |
|
| | def updateData(self, obj, prop): |
| | vobj = obj.ViewObject |
| | if prop in ["Placement"]: |
| | |
| | self.txtcoords.rotation.setValue(obj.Placement.Rotation.Q) |
| | self.onChanged(vobj, "DisplayLength") |
| |
|
| | |
| | |
| | |
| | if vobj and hasattr(vobj, "CutView") and vobj.CutView: |
| | from PySide import QtCore |
| |
|
| | |
| | QtCore.QTimer.singleShot(0, lambda: self.refreshCutView(vobj)) |
| | elif prop == "Label": |
| | if hasattr(obj.ViewObject, "ShowLabel") and obj.ViewObject.ShowLabel: |
| | self.txt.string = obj.Label |
| | return |
| |
|
| | def refreshCutView(self, vobj): |
| | """ |
| | Forces a refresh of the SoClipPlane by toggling the CutView property. |
| | This is called with a delay to ensure the object's placement is up-to-date. |
| | """ |
| | if vobj and hasattr(vobj, "CutView") and vobj.CutView: |
| | vobj.CutView = False |
| | vobj.CutView = True |
| |
|
| | def onChanged(self, vobj, prop): |
| |
|
| | if prop == "LineColor": |
| | if hasattr(vobj, "LineColor"): |
| | l = vobj.LineColor |
| | self.mat1.diffuseColor.setValue([l[0], l[1], l[2]]) |
| | self.mat2.diffuseColor.setValue([l[0], l[1], l[2]]) |
| | elif prop == "Transparency": |
| | if hasattr(vobj, "Transparency"): |
| | self.mat2.transparency.setValue(vobj.Transparency / 100.0) |
| | elif prop in ["DisplayLength", "DisplayHeight", "ArrowSize"]: |
| | |
| | if prop in ["DisplayLength", "DisplayHeight"]: |
| | if hasattr(vobj.Object.Proxy, "onChanged"): |
| | vobj.Object.Proxy.onChanged(vobj.Object, prop) |
| | if hasattr(vobj, "DisplayLength") and hasattr(vobj, "DisplayHeight"): |
| | ld = vobj.DisplayLength.Value / 2 |
| | hd = vobj.DisplayHeight.Value / 2 |
| | elif hasattr(vobj, "DisplaySize"): |
| | |
| | ld = vobj.DisplaySize.Value / 2 |
| | hd = vobj.DisplaySize.Value / 2 |
| | else: |
| | ld = 1 |
| | hd = 1 |
| | verts = [] |
| | fverts = [] |
| | pl = FreeCAD.Placement(vobj.Object.Placement) |
| | if hasattr(vobj, "ArrowSize"): |
| | l1 = vobj.ArrowSize.Value if vobj.ArrowSize.Value > 0 else 0.1 |
| | else: |
| | l1 = 0.1 |
| | l2 = l1 / 3 |
| | for v in [[-ld, -hd], [ld, -hd], [ld, hd], [-ld, hd]]: |
| | p1 = pl.multVec(Vector(v[0], v[1], 0)) |
| | p2 = pl.multVec(Vector(v[0], v[1], -l1)) |
| | p3 = pl.multVec(Vector(v[0] - l2, v[1], -l1 + l2)) |
| | p4 = pl.multVec(Vector(v[0] + l2, v[1], -l1 + l2)) |
| | p5 = pl.multVec(Vector(v[0], v[1] - l2, -l1 + l2)) |
| | p6 = pl.multVec(Vector(v[0], v[1] + l2, -l1 + l2)) |
| | verts.extend([[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]]) |
| | fverts.append([p1.x, p1.y, p1.z]) |
| | verts.extend( |
| | [[p2.x, p2.y, p2.z], [p3.x, p3.y, p3.z], [p4.x, p4.y, p4.z], [p2.x, p2.y, p2.z]] |
| | ) |
| | verts.extend( |
| | [[p2.x, p2.y, p2.z], [p5.x, p5.y, p5.z], [p6.x, p6.y, p6.z], [p2.x, p2.y, p2.z]] |
| | ) |
| | p7 = pl.multVec(Vector(-ld + l2, -hd + l2, 0)) |
| | verts.extend(fverts + [fverts[0]]) |
| | self.lcoords.point.setValues(verts) |
| | self.fcoords.point.setValues(fverts) |
| | self.txtcoords.translation.setValue([p7.x, p7.y, p7.z]) |
| | |
| | elif prop == "LineWidth": |
| | self.drawstyle.lineWidth = vobj.LineWidth |
| | elif prop in ["CutView", "CutMargin"]: |
| | if ( |
| | hasattr(vobj, "CutView") |
| | and FreeCADGui.ActiveDocument.ActiveView |
| | and hasattr(FreeCADGui.ActiveDocument.ActiveView, "getSceneGraph") |
| | ): |
| | sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() |
| | if vobj.CutView: |
| | if self.clip: |
| | sg.removeChild(self.clip) |
| | self.clip = None |
| | for o in Draft.get_group_contents(vobj.Object.Objects, walls=True): |
| | if hasattr(o.ViewObject, "Lighting"): |
| | o.ViewObject.Lighting = "One side" |
| | self.clip = coin.SoClipPlane() |
| | self.clip.on.setValue(True) |
| | norm = vobj.Object.Proxy.getNormal(vobj.Object) |
| | mp = vobj.Object.Shape.CenterOfMass |
| | mp = DraftVecUtils.project(mp, norm) |
| | dist = mp.Length |
| | norm = norm.negative() |
| | marg = 1 |
| | if hasattr(vobj, "CutMargin"): |
| | marg = vobj.CutMargin.Value |
| | if mp.getAngle(norm) > 1: |
| | dist += marg |
| | dist = -dist |
| | else: |
| | dist -= marg |
| | plane = coin.SbPlane(coin.SbVec3f(norm.x, norm.y, norm.z), dist) |
| | self.clip.plane.setValue(plane) |
| | sg.insertChild(self.clip, 0) |
| | else: |
| | if self.clip: |
| | sg.removeChild(self.clip) |
| | self.clip = None |
| | elif prop == "ShowLabel": |
| | if vobj.ShowLabel: |
| | self.txt.string = vobj.Object.Label or " " |
| | else: |
| | self.txt.string = " " |
| | elif prop == "FontName": |
| | if hasattr(self, "txtfont") and hasattr(vobj, "FontName"): |
| | if vobj.FontName: |
| | self.txtfont.name = vobj.FontName |
| | else: |
| | self.txtfont.name = "" |
| | elif prop == "FontSize": |
| | if hasattr(self, "txtfont") and hasattr(vobj, "FontSize"): |
| | self.txtfont.size = vobj.FontSize.Value |
| | return |
| |
|
| | def dumps(self): |
| |
|
| | return None |
| |
|
| | def loads(self, state): |
| |
|
| | return None |
| |
|
| | def setEdit(self, vobj, mode): |
| | if mode != 0: |
| | return None |
| |
|
| | taskd = SectionPlaneTaskPanel() |
| | taskd.obj = vobj.Object |
| | taskd.update() |
| | FreeCADGui.Control.showDialog(taskd) |
| | return True |
| |
|
| | def unsetEdit(self, vobj, mode): |
| | if mode != 0: |
| | return None |
| |
|
| | FreeCADGui.Control.closeDialog() |
| | return True |
| |
|
| | def doubleClicked(self, vobj): |
| | self.edit() |
| | return True |
| |
|
| | def setupContextMenu(self, vobj, menu): |
| | if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": |
| | return |
| |
|
| | actionEdit = QtGui.QAction(translate("Arch", "Edit"), menu) |
| | QtCore.QObject.connect(actionEdit, QtCore.SIGNAL("triggered()"), self.edit) |
| | menu.addAction(actionEdit) |
| |
|
| | actionToggleCutview = QtGui.QAction( |
| | QtGui.QIcon(":/icons/Draft_Edit.svg"), translate("Arch", "Toggle Cutview"), menu |
| | ) |
| | actionToggleCutview.triggered.connect(lambda: self.toggleCutview(vobj)) |
| | menu.addAction(actionToggleCutview) |
| |
|
| | def edit(self): |
| | FreeCADGui.ActiveDocument.setEdit(self.Object, 0) |
| |
|
| | def toggleCutview(self, vobj): |
| | vobj.CutView = not vobj.CutView |
| |
|
| |
|
| | class SectionPlaneTaskPanel: |
| | """A TaskPanel for all the section plane object""" |
| |
|
| | def __init__(self): |
| |
|
| | |
| | |
| | |
| |
|
| | self.obj = None |
| |
|
| | |
| | self.scope_widget = QtGui.QWidget() |
| | scope_layout = QtGui.QGridLayout(self.scope_widget) |
| |
|
| | self.title = QtGui.QLabel(self.scope_widget) |
| | scope_layout.addWidget(self.title, 0, 0, 1, 2) |
| |
|
| | |
| | self.tree = QtGui.QTreeWidget(self.scope_widget) |
| | scope_layout.addWidget(self.tree, 1, 0, 1, 2) |
| | self.tree.setColumnCount(1) |
| | self.tree.header().hide() |
| |
|
| | |
| | self.addButton = QtGui.QPushButton(self.scope_widget) |
| | self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg")) |
| | scope_layout.addWidget(self.addButton, 2, 0, 1, 1) |
| |
|
| | self.delButton = QtGui.QPushButton(self.scope_widget) |
| | self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg")) |
| | scope_layout.addWidget(self.delButton, 2, 1, 1, 1) |
| | self.delButton.setEnabled(False) |
| |
|
| | |
| | self.tools_widget = QtGui.QWidget() |
| | tools_layout = QtGui.QVBoxLayout(self.tools_widget) |
| |
|
| | |
| | self.cutViewButton = QtGui.QPushButton(self.tools_widget) |
| | self.cutViewButton.setIcon(QtGui.QIcon(":/icons/Arch_CutPlane.svg")) |
| | self.cutViewButton.setObjectName("cutViewButton") |
| | self.cutViewButton.setCheckable(True) |
| | QtCore.QObject.connect( |
| | self.cutViewButton, QtCore.SIGNAL("toggled(bool)"), self.toggleCutView |
| | ) |
| | tools_layout.addWidget(self.cutViewButton) |
| |
|
| | |
| | self.rotation_label = QtGui.QLabel(self.tools_widget) |
| | tools_layout.addWidget(self.rotation_label) |
| |
|
| | rotation_layout = QtGui.QHBoxLayout() |
| | self.rotateXButton = QtGui.QPushButton(self.tools_widget) |
| | self.rotateYButton = QtGui.QPushButton(self.tools_widget) |
| | self.rotateZButton = QtGui.QPushButton(self.tools_widget) |
| | rotation_layout.addWidget(self.rotateXButton) |
| | rotation_layout.addWidget(self.rotateYButton) |
| | rotation_layout.addWidget(self.rotateZButton) |
| | tools_layout.addLayout(rotation_layout) |
| |
|
| | size_pos_layout = QtGui.QHBoxLayout() |
| | self.resizeButton = QtGui.QPushButton(self.tools_widget) |
| | self.recenterButton = QtGui.QPushButton(self.tools_widget) |
| | size_pos_layout.addWidget(self.resizeButton) |
| | size_pos_layout.addWidget(self.recenterButton) |
| | tools_layout.addLayout(size_pos_layout) |
| |
|
| | QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement) |
| | QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement) |
| | QtCore.QObject.connect(self.rotateXButton, QtCore.SIGNAL("clicked()"), self.rotateX) |
| | QtCore.QObject.connect(self.rotateYButton, QtCore.SIGNAL("clicked()"), self.rotateY) |
| | QtCore.QObject.connect(self.rotateZButton, QtCore.SIGNAL("clicked()"), self.rotateZ) |
| | QtCore.QObject.connect(self.resizeButton, QtCore.SIGNAL("clicked()"), self.resize) |
| | QtCore.QObject.connect(self.recenterButton, QtCore.SIGNAL("clicked()"), self.recenter) |
| | QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemSelectionChanged()"), self.onTreeClick) |
| |
|
| | self.form = [self.scope_widget, self.tools_widget] |
| | self.update() |
| |
|
| | def isAllowedAlterSelection(self): |
| | return True |
| |
|
| | def isAllowedAlterView(self): |
| | return True |
| |
|
| | def getStandardButtons(self): |
| | return QtGui.QDialogButtonBox.Close |
| |
|
| | def getIcon(self, obj): |
| | if hasattr(obj.ViewObject, "Proxy"): |
| | return QtGui.QIcon(obj.ViewObject.Proxy.getIcon()) |
| | elif obj.isDerivedFrom("Sketcher::SketchObject"): |
| | return QtGui.QIcon(":/icons/Sketcher_Sketch.svg") |
| | elif obj.isDerivedFrom("App::DocumentObjectGroup"): |
| | return QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon) |
| | elif hasattr(obj.ViewObject, "Icon"): |
| | return QtGui.QIcon(obj.ViewObject.Icon) |
| | return QtGui.QIcon(":/icons/Part_3D_object.svg") |
| |
|
| | def update(self): |
| | "fills the treewidget" |
| | self.tree.clear() |
| | if self.obj: |
| | for o in self.obj.Objects: |
| | item = QtGui.QTreeWidgetItem(self.tree) |
| | item.setText(0, o.Label) |
| | item.setToolTip(0, o.Name) |
| | item.setIcon(0, self.getIcon(o)) |
| | if self.obj.ViewObject and hasattr(self.obj.ViewObject, "CutView"): |
| | self.cutViewButton.setChecked(self.obj.ViewObject.CutView) |
| | self.retranslateUi() |
| |
|
| | def addElement(self): |
| | if self.obj: |
| | added = False |
| | for o in FreeCADGui.Selection.getSelection(): |
| | if o != self.obj: |
| | ArchComponent.addToComponent(self.obj, o, "Objects") |
| | added = True |
| | if added: |
| | self.update() |
| | else: |
| | FreeCAD.Console.PrintWarning( |
| | "Select objects in the 3D view or in the model tree before pressing the button\n" |
| | ) |
| |
|
| | def removeElement(self): |
| | if self.obj: |
| | it = self.tree.currentItem() |
| | if it: |
| | comp = FreeCAD.ActiveDocument.getObject(str(it.toolTip(0))) |
| | ArchComponent.removeFromComponent(self.obj, comp) |
| | self.update() |
| |
|
| | def rotate(self, axis): |
| | if self.obj and self.obj.Shape and self.obj.Shape.Faces: |
| | face = self.obj.Shape.copy() |
| | import Part |
| |
|
| | local_axis = self.obj.Placement.Rotation.multVec(axis) |
| | face.rotate(self.obj.Placement.Base, local_axis, 90) |
| | self.obj.Placement = face.Placement |
| | self.obj.Proxy.execute(self.obj) |
| |
|
| | def rotateX(self): |
| | self.rotate(FreeCAD.Vector(1, 0, 0)) |
| |
|
| | def rotateY(self): |
| | self.rotate(FreeCAD.Vector(0, 1, 0)) |
| |
|
| | def rotateZ(self): |
| | self.rotate(FreeCAD.Vector(0, 0, 1)) |
| |
|
| | def getBB(self): |
| | bb = FreeCAD.BoundBox() |
| | if self.obj: |
| | for o in Draft.get_group_contents(self.obj.Objects): |
| | if hasattr(o, "Shape") and hasattr(o.Shape, "BoundBox"): |
| | bb.add(o.Shape.BoundBox) |
| | return bb |
| |
|
| | def resize(self): |
| | if self.obj and self.obj.ViewObject: |
| | bb = self.getBB() |
| | n = self.obj.Proxy.getNormal(self.obj) |
| | margin = bb.XLength * 0.1 |
| | if (n.getAngle(FreeCAD.Vector(1, 0, 0)) < 0.1) or ( |
| | n.getAngle(FreeCAD.Vector(-1, 0, 0)) < 0.1 |
| | ): |
| | self.obj.ViewObject.DisplayLength = bb.YLength + margin |
| | self.obj.ViewObject.DisplayHeight = bb.ZLength + margin |
| | elif (n.getAngle(FreeCAD.Vector(0, 1, 0)) < 0.1) or ( |
| | n.getAngle(FreeCAD.Vector(0, -1, 0)) < 0.1 |
| | ): |
| | self.obj.ViewObject.DisplayLength = bb.XLength + margin |
| | self.obj.ViewObject.DisplayHeight = bb.ZLength + margin |
| | elif (n.getAngle(FreeCAD.Vector(0, 0, 1)) < 0.1) or ( |
| | n.getAngle(FreeCAD.Vector(0, 0, -1)) < 0.1 |
| | ): |
| | self.obj.ViewObject.DisplayLength = bb.XLength + margin |
| | self.obj.ViewObject.DisplayHeight = bb.YLength + margin |
| | self.obj.Proxy.execute(self.obj) |
| |
|
| | def recenter(self): |
| | if self.obj: |
| | self.obj.Placement.Base = self.getBB().Center |
| |
|
| | def onTreeClick(self): |
| | if self.tree.selectedItems(): |
| | self.delButton.setEnabled(True) |
| | else: |
| | self.delButton.setEnabled(False) |
| |
|
| | def accept(self): |
| | FreeCAD.ActiveDocument.recompute() |
| | FreeCADGui.ActiveDocument.resetEdit() |
| | return True |
| |
|
| | def reject(self): |
| | FreeCAD.ActiveDocument.recompute() |
| | FreeCADGui.ActiveDocument.resetEdit() |
| | return True |
| |
|
| | def toggleCutView(self, checked): |
| | if self.obj and self.obj.ViewObject and hasattr(self.obj.ViewObject, "CutView"): |
| | self.obj.ViewObject.CutView = checked |
| |
|
| | def retranslateUi(self): |
| | self.scope_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Scope", None)) |
| | self.tools_widget.setWindowTitle( |
| | QtGui.QApplication.translate("Arch", "Placement and Visuals", None) |
| | ) |
| | self.title.setText( |
| | QtGui.QApplication.translate("Arch", "Objects seen by this section plane", None) |
| | ) |
| | self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None)) |
| | self.delButton.setToolTip( |
| | QtGui.QApplication.translate( |
| | "Arch", "Removes highlighted objects from the list above", None |
| | ) |
| | ) |
| | self.addButton.setText(QtGui.QApplication.translate("Arch", "Add Selected", None)) |
| | self.addButton.setToolTip( |
| | QtGui.QApplication.translate( |
| | "Arch", "Adds selected objects to the scope of this section plane", None |
| | ) |
| | ) |
| | self.cutViewButton.setText(QtGui.QApplication.translate("Arch", "Cut View", None)) |
| | self.cutViewButton.setToolTip( |
| | QtGui.QApplication.translate( |
| | "Arch", |
| | "Creates a live cut in the 3D view, hiding geometry on one side of the plane to see inside your model", |
| | None, |
| | ) |
| | ) |
| | self.rotation_label.setText(QtGui.QApplication.translate("Arch", "Rotate by 90°", None)) |
| | self.rotateXButton.setText(QtGui.QApplication.translate("Arch", "Rotate X", None)) |
| | self.rotateXButton.setToolTip( |
| | QtGui.QApplication.translate("Arch", "Rotates the plane around its local X-axis", None) |
| | ) |
| | self.rotateYButton.setText(QtGui.QApplication.translate("Arch", "Rotate Y", None)) |
| | self.rotateYButton.setToolTip( |
| | QtGui.QApplication.translate("Arch", "Rotates the plane around its local Y-axis", None) |
| | ) |
| | self.rotateZButton.setText(QtGui.QApplication.translate("Arch", "Rotate Z", None)) |
| | self.rotateZButton.setToolTip( |
| | QtGui.QApplication.translate("Arch", "Rotates the plane around its local Z-axis", None) |
| | ) |
| | self.resizeButton.setText(QtGui.QApplication.translate("Arch", "Resize to Fit", None)) |
| | self.resizeButton.setToolTip( |
| | QtGui.QApplication.translate( |
| | "Arch", "Resizes the plane to fit the objects in the list above", None |
| | ) |
| | ) |
| | self.recenterButton.setText(QtGui.QApplication.translate("Arch", "Recenter Plane", None)) |
| | self.recenterButton.setToolTip( |
| | QtGui.QApplication.translate( |
| | "Arch", "Centers the plane on the objects in the list above", None |
| | ) |
| | ) |
| |
|