41
Aspirateur ZLB
Python
1er oct 2021

Script Python pour aspirer les documents numérisés de la Zentral- und Landesbibliothek de Berlin. Extrait les pages en JPG et assemble un PDF. Les URL soumis doivent être du type : https://digital.zlb.de/viewer/image/16054086_1911/12/, c'est à dire l'adresse quand vous lisez une page depuis la visionneuse.

import os
import re
from PIL import Image # "pip install Pillow" to install package
import requests # "pip install requests" to install package
import shutil
import mimetypes
from bs4 import BeautifulSoup # "pip install beautifulsoup4" to install package
headers = {'User-Agent': 'Mozilla/5.0'}

# functions
def downloadFile(url, fileName):
    url = url.replace("\\","")
    response = requests.get(url, stream=True)
    content_type = response.headers['content-type']
    extension = mimetypes.guess_extension(content_type)
    fileName += extension
    with open(fileName, 'wb') as out_file:
        shutil.copyfileobj(response.raw, out_file)
        del response

def getRecordName(url):
    reg = re.match(r"https?:\/\/digital.zlb.de\/viewer\/image\/([A-Za-z0-9_]*)\/", url)
    if reg:
        return reg.group(1)
    else:
        print("Erreur : format d'URL incorrect. Exemple correct https://digital.zlb.de/viewer/image/16054086_1911/9/LOG_0006/")
        return None

def getMaxPagesFromRecord(record):
    url = "https://digital.zlb.de/viewer/image/"+record+"/"
    response = requests.get(url, headers=headers)
    if response:
        reg = re.search(r"rft\.tpages=(\d+)&", str(response.text))
        if reg:
            return int(reg.group(1))
        else:
            print("Impossible de trouver le nombre maximal de page automatiquement.")
            return int(input("Nombre de pages à extraire :"))
    else:
        print("Erreur : la page ZLB n'est pas chargeable.")
        return None

def findImageFromUrl(url,resolution):
    response = requests.get(url, headers=headers)
    if response:
        soup = BeautifulSoup(response.text,"html.parser")
        metaImage = soup.findAll("meta", {"property" : "og:image"})
        img = metaImage[0].get("content")
        img = img.replace("/300,/","/"+str(resolution)+",/")
        return img
    else:
        print("Erreur : la page ZLB n'est pas chargeable.")


#input
inputUrl = input("Adresse de la page ZLB à aspirer :")
resolution = int(input("Résolution (1300 recommandé) :"))

#get record infos
record = getRecordName(inputUrl)
if record:
    maxPages = getMaxPagesFromRecord(record)

#download pages as JPG
if record and maxPages:
    i = 1
    while i < (maxPages+1):
        print("Page",i,"sur",maxPages)
        url = "https://digital.zlb.de/viewer/image/"+record+"/"+str(i)+"/"
        img = findImageFromUrl(url,resolution)
        downloadFile(img, record+"_"+str(i).zfill(8))
        i += 1

#assemble PDF
filesInFolder = os.listdir()
imagesInFolder = []
imagesRGBInFolder = []
for file in filesInFolder:
    ext = os.path.splitext(file)[1]
    imageExtensions = [".jpg",".png",".gif",".jpeg",".svg"]
    if ext in imageExtensions:
        imagesInFolder.append(file)
        img = Image.open(file)
        if img.mode == "RGBA":
            img = img.convert(RGB)
        imagesRGBInFolder.append(img)

if imagesRGBInFolder:
    imagesRGBInFolder[0].save(record+".pdf", save_all=True, quality=85, append_images=imagesRGBInFolder[1:])

#delete JPG
    for file in imagesInFolder:
        os.remove(file)

Pour éviter des erreurs de mémoire avec les documents de beaucoup de pages, il faut utiliser une installation 64bits de Python.

40
De la densité à Paris
Qgis, Architecture
26 sept 2021

En urbanisme, la densité d'un territoire doit être le reflet de la quantité d'humain qui y vivent par unité de surface. En particulier, elle doit permettre de mettre en lumière l'intensité d'usage du bâti et des services et réseaux qui y sont attachés. Dans un quartier dense, on aura plus d'habitants et donc besoin de plus d'écoles, d'une plus grande fréquence de ramassage des ordures et de plus larges canalisations. C'est un indicateur des politiques urbaines pour justifier la construction de nouveaux logements ou l'implantation de services.

Son calcul se fait généralement à partir de la surface d'une zone administrative (ville, arrondissement, quartier, IRIS) et de la population qui y réside. Le nombre de résidant provient des recensements qui s'intéressent uniquement au lieu de résidence et non au lieu de travail des individus, ce qui introduit immédiatement une imprécision importante pour étudier l'intensité d'usage car elle ommet les travailleurs. Un quartier d'affaire peut être considéré comme dense (en semaine) mais aura une faible population résidente. Ce n'est cependant pas ce problème qui m'intéresse aujourd'hui.

18ème arrondissement. En blanc les grandes infrastructures, cimetières et terrains de sport
18ème arrondissement. En blanc les grandes infrastructures, cimetières et terrains de sport

Ce qui me semble plus gênant est l'usage de la surface de la zone administrative totale, dans le cas de Paris généralement celle de l'arrondissement. En effet, une part non-négligeable de la surface des arrondissements est occupée par des zones non-construites et non-habitées : périphérique, rails, terrains de sport, cimetières, ... Surtout, cette part est très inégalement repartie entre les arrondissements de Paris. Les arrondissements périphériques en possèdent généralement plus que ceux du centre.

Densité selon la surface totale de l'arrondissement, population 2015
Densité selon la surface totale de l'arrondissement, population 2015

Dans la comparaison de la densité des arrondissements, celle-ci se retrouve donc sur-évaluée dans les arrondissements avec peu d'espace non-construits (3ème, 11ème) et sous-évaluée dans ceux accueillant de grandes infrastructures (18ème, 15ème). Voir ci-dessus une carte de la densité des arrondissements de Paris, telle qu'on la voit généralement, basée sur la surface totale. La population est celle du recensement de de 2015, compilées par l'APUR.

Pour obtenir une cartographie plus réaliste, je propose de calculer la densité à partir de l'emprise bâtie totale de l'arrondissement, c'est à dire de la somme des surfaces de tous les bâtiments qui la compose. Cela permet de masquer le biais généré par la répartition inégale des espaces vides dans la ville. Cela génère un indicateur intéressant pour l'architecte et l'urbaniste car représentant précisément « l'intensité d'usage humain du bâti » et inversement sur le taux de vacances des constructions. Dans une ville si homogène en terme de gabarit de construction, cet indicateur renseigne précisément sur les zones où les immeubles sont habités ou partiellement vides.

Densité selon l'emprise bâtie totale, population 2015
Densité selon l'emprise bâtie totale, population 2015

Le tableau ci-dessous reprend les données des deux cartes et permet de faire varier le classement des arrondissements selon la surface ou l'emprise bâtie. Le 11ème arrondissement est assez largement le premier selon la surface mais ne se retrouve qu'en 4ème position selon l'emprise bâtie. Ce sont les arrondissements périphériques des 20ème, 19ème et 18ème qui occupent le podium dans ce calcul, confirmant l'intuition de départ. Le 3ème arrondissement, 5ème au classement selon la surface, descend à la 13ème place selon l'emprise. En effet, c'est un arrondissement très construit (la densité de surface est proche de la densité d'emprise bâtie) mais avec beaucoup d'équipements, de musées et de locations saisonnières. Inversement, un arrondissement comme le 13ème, pénalisés par ses grands équipements remonte dans le classement.

Arrondissement Population (2015) Surface totale Emprise bâtie totale Densité surface Densité emprise bâtie
75001 16696 1824612 633985 0.009 0.026
75002 20968 991153 591653 0.021 0.035
75003 35750 1170882 644382 0.031 0.055
75004 27501 1600585 612771 0.017 0.045
75005 60202 2539374 949065 0.024 0.063
75006 43368 2153095 919452 0.020 0.047
75007 55140 4090057 1338078 0.013 0.041
75008 37325 3880036 1650073 0.010 0.023
75009 60105 2178303 1227988 0.028 0.049
75010 92573 2891739 1414306 0.032 0.065
75011 151253 3665441 1842440 0.041 0.082
75012 143557 6388139 2005954 0.022 0.072
75013 184851 7149311 2266280 0.026 0.082
75014 141175 5614877 1774677 0.025 0.080
75015 237088 8494994 2989550 0.028 0.079
75016 167797 7873838 2657580 0.021 0.063
75017 170200 5668834 2104679 0.030 0.081
75018 198820 5996051 2184799 0.033 0.091
75019 187081 6792651 2002307 0.028 0.093
75020 196959 5983446 1972398 0.033 0.100

Avec l'exemples de Paris, ces écarts avec doivent inviter à repenser le discours sur le rapport entre construction et densité. Une zone avec de grands espaces non-bâtis n'est pas forcément moins dense qu'une zone très construite – à Paris, on constate plutôt l'inverse. Le levier de la densité se situe dans l'usage des emprises bâties : bureaux, résidences secondaires et locations saisonnières contre logements réellements occupés par des familles.

39
Matériaux PBR dans V-Ray Next
Rhino3D
5 mai 2021

Quelques notes pour l'utilisation des matériaux PBR (Physically Based Rendering) dans V-Ray Next.

Pavés PBR avec V-Ray Next pour Rhino
Pavés PBR avec V-Ray Next pour Rhino

Il existe deux modes : Specular et Metalness. On peut deviner auquel on a affaire en regardant le nom des couches (maps) du matériau. Si l'on voit une couche nommée « metal » ou « metallic », ce sera forcément le second mode.

Specular : partir d'un matériau générique (V-Ray BRDF). Les couches se placent comme ceci :

Metallic : partir d'un matériau métalique (V-Ray Metallic). Les couches se placent comme ceci :

La couche Ambient Occlusion doit être mélangée avec Diffuse/Color (ou une autre couche selon le cas). Il faut cliquer sur le pavé de texture puis séléctionner Mix (map) et placer les deux textures avec l'opération Multiply.

Les textures PBR étant généralement en grande résolution, il faut enfin s'assurer de ne pas limiter le moteur de rendu, voir Render Params > GPU Textures > Full size.

38
Cartographie des pénalités liées à la loi SRU
Architecture, Qgis
11 avr 2021

La loi relative à la solidarité et au renouvellement urbains du 13 décembre 2000, dite loi SRU, impose aux communes de disposer d'un taux minimum de logements sociaux. Ce taux originellement fixé à 20 % a été porté à 25 % en 2013. Les communes carencées sont soumises à une pénalité financière.

Les données compilées par Alexandre Léchenet à partir des balances comptables des communes permettent de générer les cartes du cumul des pénalités versées sur la dernière décennie, de 2010 à 2019.

Cumul des pénalités versées au titre de l'article 55 de la loi SRU par commune sur la période 2010–2019.
Cumul des pénalités versées au titre de l'article 55 de la loi SRU par commune sur la période 2010–2019.

Voici le Top-20 :

  1. Saint-Maur-des-Fossés (94068), 20 141 112 €
  2. Le Cannet (06030), 10 910 556 €
  3. Saint-Raphaël (83118), 5 726 747 €
  4. Boulogne-Billancourt (92012), 5 685 269 €
  5. Saint-Laurent-du-Var (06123), 4 404 465 €
  6. Mandelieu-la-Napoule (06079), 4 375 592 €
  7. Le Perreux-sur-Marne (94058), 4 245 494 €
  8. Nogent-sur-Marne (94052), 3 888 770 €
  9. Agde (34003), 3 842 009 €
  10. L'Union (31561), 3 364 781 €
  11. Courbevoie (92026), 3 349 247 €
  12. Villeneuve-Loubet (06161), 3 341 522 €
  13. Le Raincy (93062), 3 238 790 €
  14. Aix-en-Provence (13001), 3 222 150 €
  15. Grasse (06069), 3 202 149 €
  16. Roquebrune-sur-Argens (83107), 2 803 872 €
  17. Cabriès (13019), 2 637 965 €
  18. Allauch (13002), 2 613 121 €
  19. Menton (06083), 2 602 351 €
  20. Six-Fours-les-Plages (83129), 2 573 161 €

En additionnant les pénalités cumulées des communes à l'intérieur de chaque département on obtient la carte suivante.

Cumul des pénalités versées au titre de l'article 55 de la loi SRU par département sur la période 2010–2019.
Cumul des pénalités versées au titre de l'article 55 de la loi SRU par département sur la période 2010–2019.

Avec un Top-10 :

  1. Bouches-du-Rhône (13), 54 639 629 €
  2. Alpes-Maritimes (06), 53 621 511 €
  3. Val-de-Marne (94), 34 468 691 €
  4. Var (83), 33 046 587 €
  5. Hérault (34), 28 550 536 €
  6. Yvelines (78), 27 668 725 €
  7. Rhône (69), 23 778 458 €
  8. Essone (91), 21 303 253 €
  9. Loire-Atlantique (44), 16 058 016 €
  10. Haute-Garonne (31), 14 393 730 €

Pour information, le texte de loi précise que les pénalités sont prélevées par l'Etat sur le produit des taxes et cotisations foncières du territoire de la commune, après déduction des dépenses effectivement exposées par la commune pour la réalisation de logements sociaux. La pénalitée est reversée suivant sa localisation : aux établissements publics de coopération intercommunales, établissements publics foncier (en Alsace, l'EPFA), fonds nationaux et régionaux d'aménagement foncier et urbain. L'argent est ensuite utilisée pour financer des acquisitions foncières et immobilières en vue de la réalisations de logements locatifs sociaux.

37
Nettoyer un dossier de travail CAD
Windows, Cad
10 mar 2021

Pour supprimer les fichiers de sauvegarde automatique, de prévisualisation, etc. pour Autocad et Rhino.

gci *.bak -Recurse | foreach{rm $_}
gci *.3dmbak -Recurse | foreach{rm $_}
gci plot.log -Recurse | foreach{rm $_}
gci hardcopy.log -Recurse | foreach{rm $_}
gci Drawing1.dwl -Recurse | foreach{rm $_}
gci Drawing1.dwl2 -Recurse | foreach{rm $_}
gci *.cdc -Recurse | foreach{rm $_}
gci *.tmp -Recurse | foreach{rm $_}

A insérer dans un fichier .ps1 dans le dossier à nettoyer, puis clic droit > Exécuter avec Powershell. Le script s'applique au dossier et ses sous-dossiers.

36
Automatisation de publication sur Autocad
Cad, Python
24 fév 2021

Ce script Python génère un fichier SCR contenant les instructions Autocad pour la publication de tous les DSD du dossier courant. Pour charger ce fichier SCR dans Autocad, il faut utiliser la commande scr, indisponible sur Autocad LT.

#! python3

#creates a SCR file containing the Autocad commands to publish all DSD files in the current folder. The SCR file must be manually loaded in Autocad.

import os

dirpath = "."
files = os.listdir(dirpath)
scrFileName = str(os.getcwd().split(os.sep)[-1])+"_DSD.scr"

out = ""
out += "filedia 0\n"

for file in files:
    fileFull = os.path.abspath(os.path.join(dirpath, file))
    root, extension = os.path.splitext(fileFull)
    if extension == ".dsd":
        out += "-publish "+str(fileFull)+"\n"

out += "filedia 1"

with open(scrFileName, 'w') as outputFile:
    outputFile.write(out)

Pour que les DSD génèrent les fichiers PDF avec le bon nom dans le bon dossier il faut les configurer comme suit depuis la fenêtre de publication d'Autocad (commande publish) :

35
Convertir tout un dossier de fichiers FLAC en MP3 avec ffmpeg
Windows
1er fév 2021

Même si on a raté le train, il n'est pas déraisonnable de chercher à attraper le suivant. Ainsi, il faut se mettre à Powershell. D'autant que le nouveau Terminal de Windows 10 nous le propose par défaut.

Voici une commande pour convertir tous les fichiers d'un dossier contenant des FLAC en MP3, avec ffmpeg.

gci -filter *.flac -file | Foreach-Object {ffmpeg -i $_.FullName -ab 320k -map_metadata 0 -id3v2_version 3 "$($_.BaseName).mp3"}

La commande fonctionne avec tout type de fichier que ffmpeg pourrait réussir à convertir en MP3 et génère simplement une erreur lorsqu'elle recontre un fichier qu'elle ne peut pas traiter.

34
Configurer les matériaux de tous les objets en "par calque" avec Rhino Python
Python, Rhino3D
26 oct 2020
import rhinoscriptsyntax as rs
import Rhino.DocObjects

def loopBlock(block):
    objectsInBlock = rs.BlockObjects(rs.BlockInstanceName(block))

    for obj in objectsInBlock:
        if rs.ObjectType(obj) != 4096:
            action(obj)
        else:
            loopBlock(obj)

def action(object):
    rhino_object = rs.coercerhinoobject(object)
    rhino_object.Attributes.MaterialSource = Rhino.DocObjects.ObjectMaterialSource.MaterialFromLayer
    rhino_object.CommitChanges()

rs.UnselectAllObjects()

objs = rs.AllObjects(include_lights=True, include_grips=True, include_references=True)
if objs:
    for obj in objs:
        if rs.ObjectType(obj) == 4096: #block
           loopBlock(obj)
        else:
            action(obj)
else:
    print "No objects selectable."

Ce script profite de la routine permettant d'appliquer une action sur tous les objets, même ceux situés à l'intérieur de blocs. Il offre un service bien utile lorsqu'on importe de la géométrie d'un autre modeleur (comme SketchUp par exemple) qui insiste pour configurer tous les matériaux par objet.

On remarquera l'usage de la fonction rs.coercerhinoobject() qui permet de passer du GUID d'un objet à l'objet Rhino lui même. Ce passage est nécessaire pour utiliser les fonctions de l'API RhinoCommon.

Mise à jour : cette fonction est maintenant intégrée à CHL Tools (0.3)

33
Le voisin NIMBY
Architecture, Cinema
7 oct 2020
L'Arbre, le Maire et la Médiathèque, 1993, Eric Rohmer
32
Aspirateur des images du site Baunetz
Web, Python
30 sept 2020

Script Python pour automatiser le téléchargement des images des galeries du site d'architecture Baunetz et Baunetz Architekten. Nécessite Python et les librairies Requests et BeautifulSoup (commandes pip dans le code ci-dessous).

#! python3

import re
import shutil
import requests # "pip install requests" to install package
from bs4 import BeautifulSoup # "pip install beautifulsoup4" to install package
headers = {'User-Agent': 'Mozilla/5.0'}

inputURL = input("Adresse de la page Baunetz à aspirer :")

#Functions
def downloadFile(url):
    url = url.replace("\\","")
    filename = url.split("/")[-1]
    responseImg = requests.get(url, stream=True)
    with open(filename, 'wb') as out_file:
        shutil.copyfileobj(responseImg.raw, out_file)
        del responseImg

def batchDownload(urls):
    print (len(urls),"image(s) trouvée(s).")
    c = 1
    for url in urls:
        downloadFile(url)
        print("Téléchargement", c, "/", (len(urls)))
        c += 1



#Patterns
patternBaunetz = re.compile("^https?://(www.)?baunetz.de/meldungen/([^.]*).html$")
patternBaunetzArchitekten = re.compile("^https?://(www.)?baunetz-architekten.de/([^/]*)/([^/]*)/projekt/([^/]*)$")


# Baunetz
if patternBaunetz.match(inputURL):
    response = requests.get(inputURL, headers=headers)
    soup = BeautifulSoup(response.text,"html.parser")

    scriptTags = soup.find_all("script")

    for scriptTag in scriptTags:
        s = str(scriptTag)
        if s.find("xxlGalerie.xxlimages") >= 0:
           urls = re.findall(r"'url': '(https?://[^']*)'",s)
           batchDownload(urls)

    del response
else:
    print("L'adresse ne correspond pas à une page Baunetz.")


#Baunetz Architekten
if patternBaunetzArchitekten.match(inputURL):
    urls = []
    response = requests.get(inputURL, headers=headers)
    soup = BeautifulSoup(response.text,"html.parser")

    #cover img
    coverImg = soup.select("div.project-detail-image img")
    url = coverImg[0].get("data-src")
    urls.append(url)

    #imgs on landing page
    galleryImgs = soup.select("div.project-detail-gallery__image img")
    for img in galleryImgs:
        url = img.get("data-src")
        urls.append(url)

    #more imgs link (completes the slideshow)
    moreImgs = soup.select("div[data-additional-images]")
    s = str(moreImgs[0].get("data-additional-images"))
    moreImgsUrls = re.findall(r'"src":"(https:[^"]*)',s)
    for moreImgsUrl in moreImgsUrls:
        urls.append(moreImgsUrl)

    batchDownload(urls)
    del response
else:
    print("L'adresse ne correspond pas à une page Baunetz Architekten.")


# End
input("Press Enter to close")