Beispiel #1
0
class AreaLight(PointableFeatureMixin, FeatureBase):
    """An area light."""

    VIEWPROVIDER = "ViewProviderAreaLight"

    PROPERTIES = {
        "SizeU":
        Prop(
            "App::PropertyLength",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Size on U axis"),
            4.0,
        ),
        "SizeV":
        Prop(
            "App::PropertyLength",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Size on V axis"),
            2.0,
        ),
        "Color":
        Prop(
            "App::PropertyColor",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Color of light"),
            (1.0, 1.0, 1.0),
        ),
        "Power":
        Prop(
            "App::PropertyFloat",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Rendering power"),
            60.0,
        ),
        "Transparent":
        Prop(
            "App::PropertyBool",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Area light transparency"),
            False,
        ),
    }
Beispiel #2
0
class PointLight(FeatureBase):
    """A point light object."""

    VIEWPROVIDER = "ViewProviderPointLight"

    PROPERTIES = {
        "Location":
        Prop(
            "App::PropertyVector",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Location of light"),
            (0, 0, 15),
        ),
        "Color":
        Prop(
            "App::PropertyColor",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Color of light"),
            (1.0, 1.0, 1.0),
        ),
        "Power":
        Prop(
            "App::PropertyFloat",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Rendering power"),
            60.0,
        ),
        "Radius":
        Prop(
            "App::PropertyLength",
            "Light",
            QT_TRANSLATE_NOOP(
                "Render",
                "Light representation radius.\n"
                "Note: This parameter has no impact "
                "on rendering",
            ),
            2.0,
        ),
    }
Beispiel #3
0
class SunskyLight(FeatureBase):
    """A sun+sky light - Hosek-Wilkie."""

    VIEWPROVIDER = "ViewProviderSunskyLight"

    PROPERTIES = {
        "SunDirection":
        Prop(
            "App::PropertyVector",
            "Light",
            QT_TRANSLATE_NOOP(
                "Render",
                "Direction of sun from observer's point of view "
                "-- (0,0,1) is zenith",
            ),
            (1, 1, 1),
        ),
        "Turbidity":
        Prop(
            "App::PropertyFloat",
            "Light",
            QT_TRANSLATE_NOOP(
                "Render",
                "Atmospheric haziness (turbidity can go from 2.0 to 30+. 2-6 "
                "are most useful for clear days)",
            ),
            2.0,
        ),
        "GroundAlbedo":
        Prop(
            "App::PropertyFloatConstraint",
            "Light",
            QT_TRANSLATE_NOOP(
                "Render",
                "Ground albedo (reflection coefficient of the ground)",
            ),
            (0.3, 0.0, 1.0, 0.01),
        ),
    }
Beispiel #4
0
class ImageLight(FeatureBase):
    """An image-based light."""

    VIEWPROVIDER = "ViewProviderImageLight"

    PROPERTIES = {
        "ImageFile":
        Prop(
            "App::PropertyFileIncluded",
            "Light",
            QT_TRANSLATE_NOOP("Render", "Image file (included in document)"),
            "",
        ),
    }
Beispiel #5
0
class Camera(PointableFeatureMixin, FeatureBase):
    """A camera for rendering.

    This object allows to record camera settings from the Coin camera, and to
    reuse them for rendering.

    Camera Orientation is defined by a Rotation Axis and a Rotation Angle,
    applied to 'default camera'.
    Default camera looks from (0,0,1) towards the origin (target is (0,0,-1),
    and the up direction is (0,1,0).

    For more information, see Coin documentation, Camera section.
    <https://developer.openinventor.com/UserGuides/Oiv9/Inventor_Mentor/Cameras_and_Lights/Cameras.html>
    """

    VIEWPROVIDER = "ViewProviderCamera"

    PROPERTIES = {
        "Projection":
        Prop(
            "App::PropertyEnumeration",
            "Camera",
            QT_TRANSLATE_NOOP("Render",
                              "Type of projection: Perspective/Orthographic"),
            ("Perspective", "Orthographic"),
        ),
        "ViewportMapping":
        Prop(
            "App::PropertyEnumeration",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "(See Coin documentation)"),
            VIEWPORTMAPPINGENUM,
        ),
        "AspectRatio":
        Prop(
            "App::PropertyFloat",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "Ratio width/height of the camera."),
            1.0,
        ),
        "NearDistance":
        Prop(
            "App::PropertyDistance",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "Near distance, for clipping"),
            0.0,
        ),
        "FarDistance":
        Prop(
            "App::PropertyDistance",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "Far distance, for clipping"),
            200.0,
        ),
        "FocalDistance":
        Prop(
            "App::PropertyDistance",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "Focal distance"),
            100.0,
        ),
        "Height":
        Prop(
            "App::PropertyLength",
            "Camera",
            QT_TRANSLATE_NOOP("Render", "Height, for orthographic camera"),
            5.0,
        ),
        "HeightAngle":
        Prop(
            "App::PropertyAngle",
            "Camera",
            QT_TRANSLATE_NOOP(
                "Render",
                "Height angle, for perspective camera, in "
                "degrees. Important: This value will be sent as "
                "'Field of View' to the renderers.",
            ),
            60,
        ),
    }

    def on_create_cb(self, fpo, viewp, **kwargs):
        """Complete 'create' (callback)."""
        if App.GuiUp:
            viewp.set_camera_from_gui()
        else:
            set_cam_from_coin_string(fpo, DEFAULT_CAMERA_STRING)
Beispiel #6
0
class View(FeatureBase):
    """A rendering view of a FreeCAD object.

    'create' factory method should be provided a project and a source (the
    object for which the view is created), via keyword arguments.
    Please note that providing a project is mandatory to create a view: no
    rendering view should be created "off-ground".
    """

    VIEWPROVIDER = "ViewProviderView"

    PROPERTIES = {
        "Source":
        Prop(
            "App::PropertyLink",
            "Render",
            QT_TRANSLATE_NOOP("App::Property",
                              "The source object of this view"),
            None,
            0,
        ),
        "Material":
        Prop(
            "App::PropertyLink",
            "Render",
            QT_TRANSLATE_NOOP("App::Property", "The material of this view"),
            None,
            0,
        ),
        "ViewResult":
        Prop(
            "App::PropertyString",
            "Render",
            QT_TRANSLATE_NOOP("App::Property",
                              "The rendering output of this view"),
            "",
            0,
        ),
    }

    @classmethod
    def pre_create_cb(cls, **kwargs):
        """Precede the operation of 'create' (callback)."""
        project = kwargs["project"]  # Note: 'project' kw argument is mandatory
        source = kwargs["source"]  # Note: 'source' kw argument is mandatory
        assert project.Document == source.Document or FCDVERSION >= (
            0,
            19,
        ), "Unable to create View: Project and Object not in same document"

    def on_create_cb(self, fpo, viewp, **kwargs):
        """Complete 'create' (callback)."""
        project = kwargs["project"]
        source = kwargs["source"]
        fpo.Label = View.view_label(source, project)
        if project.Document != source.Document:
            # Cross-link: Transform Source into App::PropertyXLink
            name = "Source"
            fpo.removeProperty(name)
            spec = self.PROPERTIES[name]
            prop = fpo.addProperty("App::PropertyXLink", name, spec.Group,
                                   spec.Doc, 0)
            setattr(prop, name, spec.Default)
            fpo.setEditorMode(name, spec.EditorMode)
        fpo.Source = source
        project.addObject(fpo)

    def execute(self, obj):  # pylint: disable=no-self-use
        """Respond to document recomputation event (callback, mandatory).

        Write or rewrite the ViewResult string if containing project is not
        'delayed build'
        """
        # Find containing project and check DelayedBuild is false
        try:
            proj = next(x for x in obj.InListRecursive
                        if RendererHandler.is_project(x))
            assert not proj.DelayedBuild
        except (StopIteration, AttributeError, AssertionError):
            return

        # Get object rendering string and set ViewResult property
        renderer = RendererHandler(
            rdrname=proj.Renderer,
            linear_deflection=proj.LinearDeflection,
            angular_deflection=proj.AngularDeflection,
            transparency_boost=proj.TransparencySensitivity,
        )

        obj.ViewResult = renderer.get_rendering_string(obj)

    @staticmethod
    def view_label(obj, proj, is_group=False):
        """Give a standard label for the view of an object.

        Args:
            obj -- object which the view is built for
            proj -- project which the view will be inserted in
            is_group -- flag to indicate whether the view is a group

        Both obj and proj should have valid Label attributes
        """
        obj_label = str(obj.Label)
        proj_label = str(proj.Label)

        proj_label = proj_label.replace(" ", "")
        fmt = "{o}Group@{p}" if is_group else "{o}@{p}"
        res = fmt.format(o=obj_label, p=proj_label)
        return res
Beispiel #7
0
class Project(FeatureBase):
    """A rendering project."""

    VIEWPROVIDER = "ViewProviderProject"

    PROPERTIES = {
        "Renderer": Prop(
            "App::PropertyString",
            "Base",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "The name of the raytracing engine to use",
            ),
            "",
        ),
        "DelayedBuild": Prop(
            "App::PropertyBool",
            "Output",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "If true, the views will be updated on render only",
            ),
            True,
        ),
        "Template": Prop(
            "App::PropertyString",
            "Base",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "The template to be used by this rendering "
                "(use Project's context menu to modify)",
            ),
            "",
            1,
        ),
        "PageResult": Prop(
            "App::PropertyFileIncluded",
            "Render",
            QT_TRANSLATE_NOOP(
                "App::Property", "The result file to be sent to the renderer"
            ),
            "",
            2,
        ),
        "RenderWidth": Prop(
            "App::PropertyInteger",
            "Render",
            QT_TRANSLATE_NOOP(
                "App::Property", "The width of the rendered image in pixels"
            ),
            PARAMS.GetInt("RenderWidth", 800),
        ),
        "RenderHeight": Prop(
            "App::PropertyInteger",
            "Render",
            QT_TRANSLATE_NOOP(
                "App::Property", "The height of the rendered image in pixels"
            ),
            PARAMS.GetInt("RenderHeight", 600),
        ),
        "GroundPlane": Prop(
            "App::PropertyBool",
            "Ground Plane",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "If true, a default ground plane will be added to the scene",
            ),
            False,
        ),
        "GroundPlaneZ": Prop(
            "App::PropertyDistance",
            "Ground Plane",
            QT_TRANSLATE_NOOP("App::Property", "Z position for ground plane"),
            0,
        ),
        "GroundPlaneColor": Prop(
            "App::PropertyColor",
            "Ground Plane",
            QT_TRANSLATE_NOOP("App::Property", "Ground plane color"),
            (0.8, 0.8, 0.8),
        ),
        "GroundPlaneSizeFactor": Prop(
            "App::PropertyFloat",
            "Ground Plane",
            QT_TRANSLATE_NOOP("App::Property", "Ground plane size factor"),
            1.0,
        ),
        "OutputImage": Prop(
            "App::PropertyFile",
            "Output",
            QT_TRANSLATE_NOOP(
                "App::Property", "The image saved by this render"
            ),
            "",
        ),
        "OpenAfterRender": Prop(
            "App::PropertyBool",
            "Output",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "If true, the rendered image is opened in FreeCAD after "
                "the rendering is finished",
            ),
            True,
        ),
        "LinearDeflection": Prop(
            "App::PropertyFloat",
            "Mesher",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "Linear deflection for the mesher: "
                "The maximum linear deviation of a mesh section from the "
                "surface of the object.",
            ),
            0.1,
        ),
        "AngularDeflection": Prop(
            "App::PropertyFloat",
            "Mesher",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "Angular deflection for the mesher: "
                "The maximum angular deviation from one mesh section to "
                "the next, in radians. This setting is used when meshing "
                "curved surfaces.",
            ),
            math.pi / 6,
        ),
        "TransparencySensitivity": Prop(
            "App::PropertyIntegerConstraint",
            "Render",
            QT_TRANSLATE_NOOP(
                "App::Property",
                "Overweigh transparency in rendering "
                "(0=None (default), 10=Very high)."
                "When this parameter is set, low transparency ratios will "
                "be rendered more transparent. NB: This parameter affects "
                "only implicit materials (generated via Shape "
                "Appearance), not explicit materials (defined via Material"
                " property).",
            ),
            (0, 0, 10, 1),
        ),
    }

    ON_CHANGED = {
        "DelayedBuild": "_on_changed_delayed_build",
        "Renderer": "_on_changed_renderer",
    }

    def on_set_properties_cb(self, fpo):
        """Complete the operation of internal _set_properties (callback)."""
        if "Group" not in fpo.PropertiesList:
            if FCDVERSION >= (0, 19):
                fpo.addExtension("App::GroupExtensionPython")
                # See https://forum.freecadweb.org/viewtopic.php?f=10&t=54370
            else:
                fpo.addExtension("App::GroupExtensionPython", self)
        fpo.setEditorMode("Group", 2)

    def _on_changed_delayed_build(self, fpo):
        """Respond to DelayedBuild property change event."""
        if fpo.DelayedBuild:
            return
        for view in self.all_views():
            view.touch()

    def _on_changed_renderer(self, fpo):  # pylint: disable=no-self-use
        """Respond to Renderer property change event."""
        fpo.PageResult = ""

    def on_create_cb(self, fpo, viewp, **kwargs):
        """Complete the operation of 'create' (callback)."""
        rdr = str(kwargs["renderer"])
        template = str(kwargs.get("template", ""))

        fpo.Label = f"{rdr} Project"
        fpo.Renderer = rdr
        fpo.Template = template

    def get_bounding_box(self):
        """Compute project bounding box.

        This the bounding box of the underlying objects referenced in the
        scene.
        """
        bbox = App.BoundBox()
        for view in self.all_views():
            source = view.Source
            for attr_name in ("Shape", "Mesh"):
                try:
                    attr = getattr(source, attr_name)
                except AttributeError:
                    pass
                else:
                    bbox.add(attr.BoundBox)
                    break
        return bbox

    def write_groundplane(self, renderer):
        """Generate a ground plane rendering string for the scene.

        Args:
        ----------
        renderer -- the renderer handler

        Returns
        -------
        The rendering string for the ground plane
        """
        bbox = self.get_bounding_box()
        result = ""
        if bbox.isValid():
            zpos = self.fpo.GroundPlaneZ
            color = self.fpo.GroundPlaneColor
            factor = self.fpo.GroundPlaneSizeFactor
            result = renderer.get_groundplane_string(bbox, zpos, color, factor)
        return result

    def add_views(self, objs):
        """Add objects as new views to the project.

        This method can handle objects groups, recursively.

        This function checks if each object is renderable before adding it,
        via 'RendererHandler.is_renderable'; if not, a warning is issued and
        the faulty object is ignored.

        Args::
        -----------
        objs -- an iterable on FreeCAD objects to add to project
        """

        def add_to_group(objs, group):
            """Add objects as views to a group.

            objs -- FreeCAD objects to add
            group -- The group (App::DocumentObjectGroup) to add to
            """
            for obj in objs:
                success = False
                if (
                    hasattr(obj, "Group")
                    and not obj.isDerivedFrom("App::Part")
                    and not obj.isDerivedFrom("PartDesign::Body")
                ):
                    assert obj != group  # Just in case (infinite recursion)...
                    label = View.view_label(obj, group, True)
                    new_group = App.ActiveDocument.addObject(
                        "App::DocumentObjectGroup", label
                    )
                    new_group.Label = label
                    group.addObject(new_group)
                    add_to_group(obj.Group, new_group)
                    success = True
                if RendererHandler.is_renderable(obj):
                    View.create(source=obj, project=group)
                    success = True
                if not success:
                    msg = (
                        translate(
                            "Render",
                            "[Render] Unable to create rendering view for "
                            "object '{o}': unhandled object type",
                        )
                        + "\n"
                    )
                    App.Console.PrintWarning(msg.format(o=obj.fpo.Label))

        # add_views starts here
        add_to_group(iter(objs), self.fpo)

    def add_view(self, obj):
        """Add a single object as a new view to the project.

        This method is essentially provided for console mode, as above method
        'add_views' may not be handy for a single object.  However, it heavily
        relies on 'add_views', so please check the latter for more information.

        Args::
        -----------
        obj -- a FreeCAD object to add to project
        """
        objs = []
        objs.append(obj)
        self.add_views(objs)

    def all_views(self, include_groups=False):
        """Give the list of all the views contained in the project.

        Args:
            include_groups -- Flag to include containing groups (including
                the project) in returned objects. If False, only pure views are
                returned.
        """

        def all_group_objs(group, include_groups=False):
            """Return all objects in a group.

            This method recursively explodes subgroups.

            Args:
                group -- The group where the objects are to be searched.
                include_groups -- See 'all_views'
            """
            res = [] if not include_groups else [group]
            for obj in group.Group:
                res.extend(
                    [obj]
                    if not obj.isDerivedFrom("App::DocumentObjectGroup")
                    else all_group_objs(obj, include_groups)
                )
            return res

        return all_group_objs(self.fpo, include_groups)

    # TODO Remove external
    def render(self, external=True, wait_for_completion=False):
        """Render the project, calling an external renderer.

        Args:
            external -- flag to switch between internal/external version of
                renderer
            wait_for_completion -- flag to wait for rendering completion before
                return, in a blocking way (default to False)

        Returns:
            Output file path
        """
        obj = self.fpo
        wait_for_completion = bool(wait_for_completion)

        # Get a handle to renderer module
        try:
            renderer = RendererHandler(
                rdrname=obj.Renderer,
                linear_deflection=obj.LinearDeflection,
                angular_deflection=obj.AngularDeflection,
                transparency_boost=obj.TransparencySensitivity,
            )
        except ModuleNotFoundError:
            msg = (
                translate(
                    "Render",
                    "[Render] Cannot render project: Renderer '%s' not "
                    "found",
                )
                + "\n"
            )
            App.Console.PrintError(msg % obj.Renderer)
            return ""

        # Get the rendering template
        if obj.getTypeIdOfProperty("Template") == "App::PropertyFile":
            # Legacy template path (absolute path)
            template_path = obj.Template
        else:
            # Current template path (relative path)
            template_path = os.path.join(TEMPLATEDIR, obj.Template)

        if not os.path.isfile(template_path):
            msg = translate(
                "Render", "Cannot render project: Template not found ('%s')"
            )
            msg = "[Render] " + (msg % template_path) + "\n"
            App.Console.PrintError(msg)
            return ""

        with open(template_path, "r", encoding="utf8") as template_file:
            template = template_file.read()

        # Build a default camera, to be used if no camera is present in the
        # scene
        camstr = (
            Gui.ActiveDocument.ActiveView.getCamera()
            if App.GuiUp
            else DEFAULT_CAMERA_STRING
        )
        cam = renderer.get_camsource_string(get_cam_from_coin_string(camstr))

        # Get objects rendering strings (including lights, cameras...)
        # views = self.all_views()
        get_rdr_string = (
            renderer.get_rendering_string
            if obj.DelayedBuild
            else attrgetter("ViewResult")
        )
        if App.GuiUp:
            objstrings = [
                get_rdr_string(v)
                for v in self.all_views()
                if v.Source.ViewObject.Visibility
            ]
        else:
            objstrings = [get_rdr_string(v) for v in self.all_views()]

        # Add a ground plane if required
        if getattr(obj, "GroundPlane", False):
            objstrings.append(self.write_groundplane(renderer))

        # Merge all strings (cam, objects, ground plane...) into rendering
        # template
        renderobjs = "\n".join(objstrings)
        if "RaytracingCamera" in template:
            template = re.sub("(.*RaytracingCamera.*)", cam, template)
            template = re.sub("(.*RaytracingContent.*)", renderobjs, template)
        else:
            template = re.sub(
                "(.*RaytracingContent.*)", cam + "\n" + renderobjs, template
            )
        version_major = sys.version_info.major
        template = template.encode("utf8") if version_major < 3 else template

        # Write instantiated template into a temporary file
        fhandle, fpath = mkstemp(
            prefix=obj.Name, suffix=os.path.splitext(obj.Template)[-1]
        )
        with open(fpath, "w", encoding="utf8") as fobj:
            fobj.write(template)
        os.close(fhandle)
        obj.PageResult = fpath
        os.remove(fpath)
        assert obj.PageResult, "Rendering error: No page result"

        # Fetch the rendering parameters
        prefix = PARAMS.GetString("Prefix", "")
        if prefix:
            prefix += " "

        try:
            output = obj.OutputImage
            assert output
        except (AttributeError, AssertionError):
            output = os.path.splitext(obj.PageResult)[0] + ".png"

        try:
            width = int(obj.RenderWidth)
        except (AttributeError, ValueError, TypeError):
            width = 800

        try:
            height = int(obj.RenderHeight)
        except (AttributeError, ValueError, TypeError):
            height = 600

        # Get the renderer command on the generated temp file, with rendering
        # params
        cmd, img = renderer.render(
            obj, prefix, external, output, width, height
        )
        img = img if obj.OpenAfterRender else None
        if not cmd:
            # Command is empty (perhaps lack of data in parameters)
            return None

        # Execute renderer
        rdr_executor = RendererExecutor(cmd, img)
        rdr_executor.start()
        if wait_for_completion:
            # Useful in console mode...
            rdr_executor.join()

        # And eventually return result path
        return img