Metadata-Version: 2.1
Name: pyviewfactor
Version: 1.0.2
Summary: "A lightweight open-source Python library for exact view-factor computations on polygonal meshes"
Home-page: https://gitlab.com/arep-dev/pyViewFactor/
Author: Mateusz BOGDAN, Edouard WALTHER
Author-email: mateusz.bogdan@arep.fr
License: MIT
Keywords: radiative-transfer,radiation,vtk,geometry,viewfactor,visibility
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: numpy>=1.26
Requires-Dist: scipy>=1.11
Requires-Dist: numba>=0.61
Requires-Dist: pyvista>=0.45

# PyViewFactor


<div align="center">
<img src="https://gitlab.com/arep-dev/pyViewFactor/-/raw/da36d4a0b08308009d9e71876091492ecb97d476/img/Logo%20pVF%20512x512%20v3%20transparent%20crop.png" alt="pvf_logo" width="256px"/>
</div>


> `pyViewFactor` is a lightweight open-source Python library for exact view-factor computations on polygonal meshes.
> It provides robust tools for:
> - geometric visibility analysis,
> - obstruction detection,
> - accurate view factor computation.

*Full documentation available [here](https://arep-dev.gitlab.io/pyViewFactor/).*


<img src="https://gitlab.com/arep-dev/pyViewFactor/-/raw/3d40d1804eccbaefbc8b1736ff30d0d9f08141ad/img/FF_sceneUrbaine_cyl_RT.png" alt="Urban View Factor" width="800" />

 [![Latest Release](https://gitlab.com/arep-dev/pyViewFactor/-/badges/release.svg)](https://gitlab.com/arep-dev/pyViewFactor/-/releases)
 [![pipeline status](https://gitlab.com/arep-dev/pyViewFactor/badges/main/pipeline.svg)](https://gitlab.com/arep-dev/pyViewFactor/pipelines)
 [![codecov](https://codecov.io/gl/arep-dev/pyViewFactor/graph/badge.svg?token=HPHU835UZ8)](https://codecov.io/gl/arep-dev/pyViewFactor)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 [![Pypi Version](https://img.shields.io/pypi/v/pyviewfactor)](https://pypi.org/project/pyviewfactor/)
 [![Pypi Downloads](https://img.shields.io/pypi/dm/pyviewfactor.svg?label=pypi%20downloads)](https://pypi.org/project/pyviewfactor/)
 


## Features

* This library enables the computation of radiation view factors between planar polygons using an **accurate** double‐contour integration method described in
[(Mazumder and Ravishankar 2012)](https://www.academia.edu/download/77459051/Viewfactor_paper_IJHMT.pdf)
with insights from [(Schmid 2016)](https://hal.archives-ouvertes.fr/tel-01734545/).
* It uses the handy [`Pyvista`](https://docs.pyvista.org/) package to deal with geometry imports (`*.stl`, `*.vtk`, `*.obj`, ...),
geometry creations, and some other mesh functionalities under the hood. 
* It enables:
    - 🔺 View factor computation between planar polygons
    - 👁️ Visibility checks based on face orientation
    - 🚧 Obstruction detection using ray-triangle intersection
    - ⚙️ Strict / non-strict modes for robustness control
    - ⚡ Optimized full matrix computation with caching
    - 📦 Built on `numpy`, `scipy`, `pyvista`, `numba`



## Installation
`pyViewFactor` can be installed from [PyPi](https://pypi.org/project/pyviewfactor/) 
using `pip` on Python >= 3.10:
```shell
pip install pyviewfactor
```
You can also visit [PyPi](https://pypi.org/project/pyviewfactor/) or
[Gitlab](https://gitlab.com/arep-dev/pyViewFactor) to download the sources.

Requirements: 
```
numpy==1.26.4
pyvista==0.45
scipy==1.11.4
numba==0.61.2
joblib==1.2.0
tqdm==4.65.0

```
The code will probably work with lower versions of the required packages, however this has not been tested.

> [!NOTE]
> If you are alergic to `numba`, you may `pip install pyviewfactor==0.0.10` that works (and give up the times 3+ speed-up in view factor computation).


## Quick Start

Suppose we want to compute the radiation view factor between a triangle
and a rectangle facing each other:

<img src="https://gitlab.com/arep-dev/pyViewFactor/-/raw/9dbd31d124eb898cc5d17913cfc1f010116e2245/img/mwe.png" alt="Triangle and rectangle configuration" width="260"/>

You are few lines of code away from your first view factor computation:

```python
import pyvista as pv
import pyviewfactor as pvf

# Create a rectangle and a triangle facing each other
pointa1 = [0.0, 0.0, 0.0]
pointb1 = [1.0, 0.0, 0.0]
pointc1 = [0.0, 1.0, 0.0]
rectangle = pv.Rectangle([pointa1, pointb1, pointc1])

pointa2 = [0.0, 0.0, 1.0]
pointb2 = [0.0, 1.0, 1.0]
pointc2 = [1.0, 1.0, 1.0]
triangle = pv.Triangle([pointa2, pointb2, pointc2])

if pvf.get_visibility(rectangle, triangle)[0]:
    F = pvf.compute_viewfactor(triangle, rectangle)
    print("VF from rectangle to triangle :", F)
else:
    print("Not facing each other")

pl = pv.Plotter()
pl.add_mesh(rectangle, color="lightblue", opacity=0.7)
pl.add_mesh(triangle, color="salmon", opacity=0.7)

# compute and glyph normals for mesh1
n1 = rectangle.compute_normals(cell_normals=True, point_normals=False)
arrows1 = n1.glyph(orient="Normals", factor=0.1)
pl.add_mesh(arrows1, color="blue")
# similarly for mesh2
n2 = triangle.compute_normals(cell_normals=True, point_normals=False)
arrows2 = n2.glyph(orient="Normals", factor=0.1)
pl.add_mesh(arrows2, color="darkred")

pl.show()
```

You usually get your geometry from a different format?
(`*.dat`, `*.idf`, ...)

Check pyvista's documentation on [how to generate a PolyData facet from points](https://docs.pyvista.org/examples/00-load/create-poly.html).


### Example 1 : View factors of an individual with a wall

For comfort computations, it may be useful to determine heat transfer between an
individual and a wall. We will use here PyVista's
[doorman example](https://docs.pyvista.org/api/examples/_autosummary/pyvista.examples.downloads.download_doorman.html?highlight=example+man#pyvista.examples.downloads.download_doorman)
as a basis for the human geometry.

<img src="https://gitlab.com/arep-dev/pyViewFactor/-/raw/9dbd31d124eb898cc5d17913cfc1f010116e2245/img/Fground.png" alt="View factors of the doorman's faces to the ground" width="380"/>

The following code and `.vtk` file of the doorman example are available in
the [./examples/](https://gitlab.com/arep-dev/pyViewFactor/-/tree/main/examples) folder.

```python
from tqdm import tqdm
import numpy as np
import pyvista as pv
import pyviewfactor as pvf

def fc_Fwall(nom_vtk):
    # This function is bit more generic than this specific use case,
    # so it can be reused for other applications
    mesh = pv.read(nom_vtk)

    # find all types of walls : in this example only a ground
    wall_types = list(np.unique(mesh["geom_id"]))
    # remove the individual from the list (still named "cylinder"...)
    wall_types.remove("doorman")
    # where is the doorman in the list?
    index_doorman = np.where(mesh["geom_id"] == "doorman")[0]
    # prepare storage for the different walls in a dict
    dict_F = {}
    # loop over wall types
    for type_wall in wall_types:
        # prepare for storing doorman to wall view factor
        F = np.zeros(mesh.n_cells)
        # get the indices of this type of wall
        indices = np.where(mesh["geom_id"] == type_wall)[0]
        # loop over
        for i in indices:
            wall = mesh.extract_cells(i)
            wall = pvf.fc_unstruc2poly(wall)  # convert for normals
            # ... for each facet of the individual
            for idx in tqdm(index_doorman):
                face = mesh.extract_cells(idx)
                face = pvf.fc_unstruc2poly(face)  # convert for normals
                # check if faces can "see" each other
                if pvf.get_visibility(wall, face):
                    # compute face2wall view factor
                    Ffp = pvf.compute_viewfactor(wall, face)
                else:
                    Ffp = 0
                F[idx] = Ffp
        # store array F in e.g. dict_F["F_ceiling"]
        dict_F["F_" + type_wall.replace("\r", "")] = F
    return dict_F

# You can get the  doorman geomtry it directly from here:
# https://gitlab.com/arep-dev/pyViewFactor/-/blob/main/examples/example_doorman_clean.vtk
# ... or get it from this repository's examples
file = "./src_data/example_doorman_clean.vtk"

# compute the VFs for the doorman to the different wall types in the scene
dict_F = fc_Fwall(file)

# re-read and store
mesh = pv.read(file)
# loop over what is in the dictionary of view factors
for elt in dict_F.keys():
    mesh[elt.replace("\r", "")] = dict_F[elt]  # name the field
mesh.save("./src_data/example_doorman_VFground.vtk")  # store in the intial VTK

# have a look without paraview with fancy colors
mesh.plot(cmap="magma_r", lighting=False)
```

More details and view factors abacuses can be found [here](https://lhypercube.arep.fr/en/confort/calcul-de-la-temperature-radiante/).


### Example 2 : Urban Scene 

For building simulation purposes, it may prove to be useful to compute the ground and sky view factors of a given wall, or the view factor of the wall to other walls in the built environment. In following example (available in the [`/examples/` folder](https://gitlab.com/arep-dev/pyViewFactor/-/tree/main/examples)), we compute the view factors of the environment of the purple wall depicted below.

<img src="https://gitlab.com/arep-dev/pyViewFactor/-/raw/9dbd31d124eb898cc5d17913cfc1f010116e2245/img/wall_view_factors.png" alt="View factors in built environment" width="430"/>

```python

import numpy as np
import pyvista as pv
import pyviewfactor as pvf

# -------------------------
# Load geometry
# -------------------------
mesh = pv.read("./src_data/built_envmt.vtk")
meshpoly = pvf.fc_unstruc2poly(mesh)

# Identify groups
i_wall = np.where(mesh["wall_names"] == "wall")[0]
i_sky = np.where(mesh["wall_names"] == "sky")[0]
i_building1 = np.where(mesh["wall_names"] == "building1")[0]
i_building2 = np.where(mesh["wall_names"] == "building2")[0]

# Extract wall
wall = mesh.extract_cells(i_wall).extract_surface()

# Parameters
strict_visibility = False
strict_obstruction = False
rounding_decimal = 5

# -------------------------
# Wall → Sky
# -------------------------
Fsky = 0.0
print("> Computation F_wall>sky")

for patch in i_sky:
    sky = mesh.extract_cells(patch).extract_surface()

    if pvf.get_visibility(sky, wall,
                          strict=strict_visibility,
                          rounding_decimal=rounding_decimal)[0]:

        if pvf.get_obstruction(sky, wall, meshpoly,
                               strict=strict_obstruction,
                               rounding_decimal=rounding_decimal)[0]:

            Fsky += pvf.compute_viewfactor(sky, wall)

# -------------------------
# Wall → Building 1
# -------------------------
Fbuilding1 = 0.0
print("> Computation F_wall>building1")

for patch in i_building1:
    bld = pvf.fc_unstruc2poly(mesh.extract_cells(patch))

    if pvf.get_visibility(bld, wall,
                          strict=strict_visibility,
                          rounding_decimal=rounding_decimal)[0]:

        if pvf.get_obstruction(bld, wall, meshpoly,
                               strict=strict_obstruction,
                               rounding_decimal=rounding_decimal)[0]:

            Fbuilding1 += pvf.compute_viewfactor(bld, wall)

# -------------------------
# Wall → Building 2
# -------------------------
Fbuilding2 = 0.0
print("> Computation F_wall>building2")

for patch in i_building2:
    bld = pvf.fc_unstruc2poly(mesh.extract_cells(patch))

    if pvf.get_visibility(bld, wall,
                          strict=strict_visibility,
                          rounding_decimal=rounding_decimal)[0]:

        if pvf.get_obstruction(bld, wall, meshpoly,
                               strict=strict_obstruction,
                               rounding_decimal=rounding_decimal)[0]:

            Fbuilding2 += pvf.compute_viewfactor(bld, wall)

# -------------------------
# Ground by complementarity
# -------------------------
Fground = 1.0 - Fsky - Fbuilding1 - Fbuilding2

# -------------------------
# Results
# -------------------------
print("\n--- View Factors ---")
print(f"Sky        : {Fsky:.4f}")
print(f"Building 1 : {Fbuilding1:.4f}")
print(f"Building 2 : {Fbuilding2:.4f}")
print(f"Ground     : {Fground:.4f}")
```

The code yields following view factors :
```math
F_{\text{sky}} = 0.3173 \\
F_{\text{ground}} = 0.4009 \\
F_{\text{building1}} = 0.2506 \\
F_{\text{building2}} = 0.0312 \\
```

## Documentation

For detailed explanations and advanced usage, see:

https://arep-dev.gitlab.io/pyViewFactor/pyviewfactor.html

The documentation includes:
* visibility and obstruction semantics,
* strict vs non-strict modes,
* geometry preprocessing utilities,
* numerical robustness guidelines,
* extended examples (that can also be found in the `examples/` folder).


## Citation & Acknowledgments
* Main contributors:
    * Mateusz BOGDAN,
    * Edouard WALTHER.
* Acknowledgment: The authors would like to acknowledge *M. Alecian* for his initial work on the quadrature
code and *M. Chapon* for her contribution to the code validation.

There is even a [conference paper](https://www.researchgate.net/publication/360835982_Calcul_des_facteurs_de_forme_entre_polygones_-Application_a_la_thermique_urbaine_et_aux_etudes_de_confort), showing analytical validations. 

So if you use `pyViewFactor` in your work, please cite:
> [!IMPORTANT] Citation:
> Mateusz BOGDAN, Edouard WALTHER, Marc ALECIAN and Mina CHAPON. *Calcul des facteurs de forme entre polygones - Application à la thermique urbaine et aux études de confort*. IBPSA France 2022, Châlons-en-Champagne. 

Bibtex entry:
``` latex
@inproceedings{pyViewFactor22bogdan,
  authors      = "Mateusz BOGDAN and Edouard WALTHER and Marc ALECIAN and Mina CHAPON",
  title        = "Calcul des facteurs de forme entre polygones - Application à la thermique urbaine et aux études de confort",
  year         = "2022",
  organization = "IBPSA France",
  venue        = "Châlons-en-Champagne, France"
  note         = "IBPSA France 2022",
}
```

## License
MIT License - Copyright (c) AREP 2025
