feat: initial commit
@@ -0,0 +1,15 @@
|
||||
# Project Guidelines
|
||||
|
||||
## Scope
|
||||
- Ovaj folder sluzi za analizu i koncept uredjenja potkrovlja kuce.
|
||||
- U njemu drzati proracune kvadrature, troskovnike, skice i lokalne vizuelne materijale.
|
||||
|
||||
## Deliverables
|
||||
- Procena kvadrature za izolaciju kosog krova.
|
||||
- Predlog sloja izolacije 15-20 cm sa drvenom zavrsnom oblogom umesto gipsa.
|
||||
- Pregled aktuelnih cena materijala i rada u Srbiji sa izvorima.
|
||||
- Koncept uredjenja potkrovlja sa lokalno generisanim vizuelnim prikazima.
|
||||
|
||||
## Local Rules
|
||||
- Ne unositi privatne ili Codex-only beleske u git.
|
||||
- Sve privremene fajlove drzati u ovom folderu i obrisati ih na kraju uz potvrdu korisnika.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Status - 2026-03-25
|
||||
|
||||
## Gde smo stali
|
||||
- Formiran je projektni folder: `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/`
|
||||
- Upisan je projektni `AGENTS.md`.
|
||||
- Pregledane su dostavljene osnove i fotografije potkrovlja.
|
||||
- Provereno je da lokalno postoje alati za izradu vizuala: `python3`, `node`, `chromium`, `PIL`, `matplotlib`, `numpy`, `convert`.
|
||||
- Pokrenuta je online provera cena materijala i rada za Srbiju.
|
||||
|
||||
## Radna geometrijska pretpostavka krova
|
||||
- Iz osnove uzeto: bruto povrsina potkrovlja `79.29 m2`.
|
||||
- Iz tabele uzeto: terasa `5.89 m2`.
|
||||
- Na osnovu nacrta i oznake `visina slemena 0.85 m` koristi se radni nagib krova oko `17.4°`.
|
||||
- Faktor kosine: `1 / cos(17.4°) = 1.048`.
|
||||
|
||||
## Izracunate varijante povrsine za izolaciju kosina
|
||||
1. Ako se racuna puna bruto projekcija krova:
|
||||
83.09 m2 kosih povrsina
|
||||
91.40 m2 sa 10% rezerve
|
||||
|
||||
2. Ako terasa nije pod krovom i oduzima se iz projekcije:
|
||||
76.92 m2 kosih povrsina
|
||||
84.61 m2 sa 10% rezerve
|
||||
|
||||
## Kolicine izolacije iz trenutnog proracuna
|
||||
1. Za 15 cm ukupne izolacije:
|
||||
- puna bruto varijanta: `12.46 m3` ili `13.71 m3` sa rezervom
|
||||
- varijanta bez terase: `11.54 m3` ili `12.69 m3` sa rezervom
|
||||
|
||||
2. Za 20 cm ukupne izolacije:
|
||||
- puna bruto varijanta: `16.62 m3` ili `18.28 m3` sa rezervom
|
||||
- varijanta bez terase: `15.38 m3` ili `16.92 m3` sa rezervom
|
||||
|
||||
## Tehnicki smer rada koji je planiran za nastavak
|
||||
- Predlog sistema: izolacija izmedju/ispod rogova 15-20 cm, vazdusna ili kontrolisana distanca prema daskama ako membrana nije potvrdena, parna kontrola sa unutrasnje strane, podkonstrukcija, zavrsna drvena obloga umesto gipsa.
|
||||
- Verovatno ce kao osnovna preporuka ici varijanta sa kamenom vunom + pametna parna brana + drvena obloga.
|
||||
- Kao premium alternativa moze se razmotriti drveno vlakno zbog boljeg letnjeg komfora.
|
||||
|
||||
## Novo uradjeno 2026-03-25
|
||||
- Zavrsena online provera cena materijala i repera za ruke u Srbiji.
|
||||
- Napisan glavni izvestaj: `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/potkrovlje_izvestaj.md`
|
||||
- Generisani lokalni koncept-vizuali:
|
||||
- `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/renders/contact-sheet.png`
|
||||
- `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/renders/zoning-plan.png`
|
||||
- `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/renders/render-1-warm-nordic.png`
|
||||
- `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/renders/render-2-soft-japandi.png`
|
||||
- `/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026/renders/render-3-rustic-studio.png`
|
||||
|
||||
## Zakljucak do sada
|
||||
- Najracionalnija nabavna kvadratura za krovnu izolaciju je `oko 85 m2`.
|
||||
- Ako se na licu mesta potvrdi da terasa ipak ulazi u krovni omotac, idi na `91-92 m2`.
|
||||
- Preporucena tehnicka varijanta je `20 cm` ukupne izolacije + `LDS 5 Silk` ili ekvivalentna aktivna parna kontrola + drvena obloga.
|
||||
- Za preporucenu varijantu 20 cm realan budzet je `oko 502,000-586,000 RSD`, a bezbednije planiranje `550,000-620,000 RSD`.
|
||||
|
||||
## Sledeci koraci ako korisnik zeli nastavak
|
||||
- Razraditi jedan izabrani koncept do detaljnijeg rasporeda namestaja.
|
||||
- Napraviti precizniju nabavnu listu po rolnama, pakovanjima i komadima.
|
||||
- Po potrebi doraditi render u jednom izabranom stilu.
|
||||
@@ -0,0 +1,258 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
ROOT = Path("/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026")
|
||||
OUT = ROOT / "renders"
|
||||
OUT.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def load_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
||||
paths = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf",
|
||||
]
|
||||
for path in paths:
|
||||
try:
|
||||
return ImageFont.truetype(path, size)
|
||||
except OSError:
|
||||
continue
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
TITLE = load_font(54, bold=True)
|
||||
SUB = load_font(28, bold=True)
|
||||
TEXT = load_font(24)
|
||||
SMALL = load_font(18)
|
||||
|
||||
|
||||
def lerp(a: tuple[int, int], b: tuple[int, int], t: float) -> tuple[int, int]:
|
||||
return (int(a[0] + (b[0] - a[0]) * t), int(a[1] + (b[1] - a[1]) * t))
|
||||
|
||||
|
||||
def draw_planks(draw: ImageDraw.ImageDraw, p1, p2, p3, p4, count: int, color: tuple[int, int, int], width: int = 2) -> None:
|
||||
for i in range(1, count):
|
||||
a = lerp(p1, p2, i / count)
|
||||
b = lerp(p4, p3, i / count)
|
||||
draw.line([a, b], fill=color, width=width)
|
||||
|
||||
|
||||
def draw_brick_wall(draw: ImageDraw.ImageDraw, x1: int, y1: int, x2: int, y2: int, base: tuple[int, int, int], mortar: tuple[int, int, int]) -> None:
|
||||
draw.rectangle([x1, y1, x2, y2], fill=base)
|
||||
brick_h = 28
|
||||
brick_w = 74
|
||||
row = 0
|
||||
for y in range(y1, y2, brick_h):
|
||||
offset = 0 if row % 2 == 0 else brick_w // 2
|
||||
for x in range(x1 - offset, x2, brick_w):
|
||||
draw.rectangle([x, y, min(x + brick_w - 6, x2), min(y + brick_h - 4, y2)], outline=mortar, width=2)
|
||||
row += 1
|
||||
|
||||
|
||||
def draw_window(draw: ImageDraw.ImageDraw, box, frame, glass, muntin) -> None:
|
||||
x1, y1, x2, y2 = box
|
||||
draw.rounded_rectangle(box, radius=6, fill=frame, outline=(80, 58, 38), width=3)
|
||||
inset = 10
|
||||
draw.rectangle([x1 + inset, y1 + inset, x2 - inset, y2 - inset], fill=glass)
|
||||
mid = (x1 + x2) // 2
|
||||
draw.line([(mid, y1 + inset), (mid, y2 - inset)], fill=muntin, width=4)
|
||||
draw.line([(x1 + inset, (y1 + y2) // 2), (x2 - inset, (y1 + y2) // 2)], fill=(255, 255, 255, 85), width=1)
|
||||
|
||||
|
||||
def draw_perspective_room(name: str, palette: dict[str, tuple[int, int, int]], furniture: str) -> None:
|
||||
img = Image.new("RGB", (1600, 1000), palette["bg"])
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
draw.rectangle([0, 0, 1600, 1000], fill=palette["bg"])
|
||||
draw.rectangle([0, 0, 1600, 300], fill=palette["sky"])
|
||||
|
||||
left_roof = [(120, 300), (470, 160), (530, 540), (250, 780)]
|
||||
right_roof = [(1130, 160), (1480, 300), (1350, 780), (1070, 540)]
|
||||
back_wall = [(470, 160), (1130, 160), (1300, 300), (1100, 540), (500, 540), (300, 300)]
|
||||
floor = [(250, 780), (1350, 780), (1100, 540), (500, 540)]
|
||||
left_knee = [(110, 680), (250, 780), (500, 540), (365, 460)]
|
||||
right_knee = [(1235, 460), (1100, 540), (1350, 780), (1490, 680)]
|
||||
|
||||
draw.polygon(floor, fill=palette["floor"])
|
||||
draw_planks(draw, floor[0], floor[1], floor[2], floor[3], 11, palette["floor_lines"], 3)
|
||||
draw.polygon(back_wall, fill=palette["brick"])
|
||||
draw_brick_wall(draw, 350, 210, 1240, 540, palette["brick"], palette["mortar"])
|
||||
draw.polygon(left_knee, fill=palette["knee"])
|
||||
draw.polygon(right_knee, fill=palette["knee"])
|
||||
draw.polygon(left_roof, fill=palette["ceiling"])
|
||||
draw.polygon(right_roof, fill=palette["ceiling"])
|
||||
draw_planks(draw, left_roof[0], left_roof[1], left_roof[2], left_roof[3], 12, palette["ceiling_lines"])
|
||||
draw_planks(draw, right_roof[1], right_roof[0], right_roof[3], right_roof[2], 12, palette["ceiling_lines"])
|
||||
|
||||
draw.line([(470, 160), (1130, 160)], fill=palette["beam"], width=18)
|
||||
for x in [250, 420, 590, 760, 930, 1100, 1270]:
|
||||
draw.line([(x, 250), (x + 80, 540)], fill=palette["beam_soft"], width=18)
|
||||
|
||||
draw_window(draw, (620, 240, 760, 465), palette["frame"], palette["glass"], palette["muntin"])
|
||||
draw_window(draw, (850, 240, 1010, 465), palette["frame"], palette["glass"], palette["muntin"])
|
||||
draw.rounded_rectangle((420, 230, 570, 510), radius=8, fill=palette["frame"], outline=(70, 45, 25), width=3)
|
||||
draw.rectangle((435, 245, 555, 495), fill=palette["glass"])
|
||||
draw.line([(490, 245), (490, 495)], fill=palette["muntin"], width=3)
|
||||
|
||||
draw.rectangle((315, 575, 495, 635), fill=palette["storage"])
|
||||
draw.rectangle((1110, 575, 1285, 635), fill=palette["storage"])
|
||||
|
||||
if furniture == "lounge":
|
||||
draw.rounded_rectangle((540, 650, 980, 760), radius=20, fill=palette["sofa"])
|
||||
draw.rectangle((660, 720, 860, 760), fill=palette["sofa_shadow"])
|
||||
draw.ellipse((720, 705, 900, 785), fill=palette["table"])
|
||||
draw.rectangle((1180, 595, 1290, 720), fill=palette["desk"])
|
||||
draw.rectangle((1215, 655, 1260, 765), fill=palette["chair"])
|
||||
label = "Koncept 1 Warm Nordic Loft"
|
||||
note = "Otvoreni lounge + desk zona uz pogled i terasu."
|
||||
elif furniture == "bedroom":
|
||||
draw.rounded_rectangle((545, 635, 1045, 760), radius=20, fill=palette["bed"])
|
||||
draw.rectangle((565, 615, 1025, 650), fill=palette["headboard"])
|
||||
draw.rectangle((630, 660, 780, 735), fill=palette["linen_1"])
|
||||
draw.rectangle((800, 660, 950, 735), fill=palette["linen_2"])
|
||||
draw.rectangle((340, 610, 450, 705), fill=palette["bench"])
|
||||
draw.rectangle((1145, 600, 1265, 710), fill=palette["bench"])
|
||||
label = "Koncept 2 Soft Japandi Suite"
|
||||
note = "Mirna spavaca zona sa niskim plakarima pod kosinama."
|
||||
else:
|
||||
draw.rounded_rectangle((560, 660, 930, 760), radius=20, fill=palette["sofa"])
|
||||
draw.rectangle((960, 615, 1120, 760), fill=palette["shelf"])
|
||||
draw.rectangle((1030, 560, 1185, 720), fill=palette["shelf"])
|
||||
draw.ellipse((620, 705, 810, 785), fill=palette["table"])
|
||||
draw.rectangle((345, 600, 520, 740), fill=palette["desk"])
|
||||
draw.rectangle((390, 700, 455, 790), fill=palette["chair"])
|
||||
label = "Koncept 3 Rustic Studio Loft"
|
||||
note = "Studio i biblioteka sa jacim kontrastom drveta i opeke."
|
||||
|
||||
draw.rectangle((0, 0, 1600, 88), fill=(15, 18, 24))
|
||||
draw.text((48, 18), label, font=SUB, fill=(245, 240, 232))
|
||||
draw.text((48, 915), note, font=TEXT, fill=(30, 30, 32))
|
||||
|
||||
chips = [palette["ceiling"], palette["brick"], palette["floor"], palette["storage"], palette["accent"]]
|
||||
x = 1050
|
||||
for chip in chips:
|
||||
draw.rounded_rectangle((x, 905, x + 56, 961), radius=12, fill=chip)
|
||||
x += 72
|
||||
|
||||
draw.text((48, 952), "Koncept render: baziran na dostavljenim fotografijama i osnovi, bez laserskog snimka.", font=SMALL, fill=(72, 72, 76))
|
||||
img.save(OUT / f"{name}.png", quality=95)
|
||||
|
||||
|
||||
def draw_zoning_plan() -> None:
|
||||
img = Image.new("RGB", (1400, 1000), (246, 241, 235))
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rectangle((120, 140, 1280, 860), outline=(45, 45, 45), width=5, fill=(252, 250, 246))
|
||||
draw.rectangle((180, 620, 430, 840), outline=(150, 40, 40), width=6, fill=(242, 238, 234))
|
||||
draw.text((215, 705), "Stepenice", font=SUB, fill=(120, 25, 25))
|
||||
draw.rectangle((430, 160, 1180, 420), fill=(232, 214, 196), outline=(140, 122, 101), width=4)
|
||||
draw.text((620, 255), "Lounge + terasa", font=TITLE, fill=(76, 58, 46))
|
||||
draw.rectangle((540, 470, 1180, 780), fill=(223, 232, 226), outline=(93, 118, 106), width=4)
|
||||
draw.text((690, 585), "Spavanje", font=TITLE, fill=(52, 78, 68))
|
||||
draw.rectangle((180, 160, 420, 500), fill=(219, 225, 236), outline=(85, 100, 124), width=4)
|
||||
draw.text((208, 300), "Garderoba /\nodlaganje", font=SUB, fill=(60, 76, 96))
|
||||
draw.rectangle((1190, 160, 1235, 830), fill=(212, 201, 186))
|
||||
draw.rectangle((165, 160, 210, 830), fill=(212, 201, 186))
|
||||
draw.text((80, 90), "Predlog zoniranja potkrovlja", font=TITLE, fill=(28, 28, 28))
|
||||
draw.text((120, 900), "Niske ivice pod kosinama: plakar 45-60 cm dubine celom duzinom.", font=TEXT, fill=(40, 40, 40))
|
||||
img.save(OUT / "zoning-plan.png", quality=95)
|
||||
|
||||
|
||||
PALETTES = {
|
||||
"render-1-warm-nordic": {
|
||||
"bg": (245, 240, 233),
|
||||
"sky": (227, 234, 239),
|
||||
"floor": (142, 112, 84),
|
||||
"floor_lines": (95, 71, 49),
|
||||
"brick": (178, 132, 98),
|
||||
"mortar": (208, 188, 169),
|
||||
"knee": (171, 157, 140),
|
||||
"ceiling": (214, 180, 138),
|
||||
"ceiling_lines": (162, 123, 84),
|
||||
"beam": (104, 64, 44),
|
||||
"beam_soft": (120, 76, 54),
|
||||
"frame": (126, 88, 52),
|
||||
"glass": (176, 205, 219),
|
||||
"muntin": (230, 236, 240),
|
||||
"storage": (181, 164, 146),
|
||||
"sofa": (208, 191, 170),
|
||||
"sofa_shadow": (183, 164, 144),
|
||||
"table": (208, 178, 137),
|
||||
"desk": (121, 96, 72),
|
||||
"chair": (94, 79, 64),
|
||||
"accent": (162, 121, 84),
|
||||
"bed": (222, 212, 198),
|
||||
"headboard": (130, 103, 82),
|
||||
"linen_1": (236, 229, 220),
|
||||
"linen_2": (198, 180, 159),
|
||||
"bench": (168, 136, 104),
|
||||
"shelf": (116, 88, 67),
|
||||
},
|
||||
"render-2-soft-japandi": {
|
||||
"bg": (243, 241, 236),
|
||||
"sky": (226, 231, 228),
|
||||
"floor": (156, 140, 114),
|
||||
"floor_lines": (117, 99, 74),
|
||||
"brick": (194, 184, 171),
|
||||
"mortar": (228, 223, 214),
|
||||
"knee": (188, 184, 170),
|
||||
"ceiling": (224, 210, 183),
|
||||
"ceiling_lines": (176, 161, 133),
|
||||
"beam": (124, 96, 70),
|
||||
"beam_soft": (144, 114, 86),
|
||||
"frame": (133, 105, 74),
|
||||
"glass": (190, 205, 202),
|
||||
"muntin": (235, 239, 237),
|
||||
"storage": (203, 198, 188),
|
||||
"sofa": (214, 208, 198),
|
||||
"sofa_shadow": (184, 177, 167),
|
||||
"table": (189, 171, 146),
|
||||
"desk": (156, 142, 122),
|
||||
"chair": (121, 117, 101),
|
||||
"accent": (125, 141, 122),
|
||||
"bed": (219, 213, 203),
|
||||
"headboard": (150, 138, 118),
|
||||
"linen_1": (241, 236, 228),
|
||||
"linen_2": (193, 204, 193),
|
||||
"bench": (174, 166, 148),
|
||||
"shelf": (126, 120, 106),
|
||||
},
|
||||
"render-3-rustic-studio": {
|
||||
"bg": (236, 232, 227),
|
||||
"sky": (219, 226, 234),
|
||||
"floor": (116, 98, 86),
|
||||
"floor_lines": (80, 63, 52),
|
||||
"brick": (153, 108, 82),
|
||||
"mortar": (196, 176, 164),
|
||||
"knee": (111, 110, 108),
|
||||
"ceiling": (170, 143, 109),
|
||||
"ceiling_lines": (116, 92, 65),
|
||||
"beam": (69, 44, 32),
|
||||
"beam_soft": (86, 58, 40),
|
||||
"frame": (112, 72, 43),
|
||||
"glass": (175, 197, 212),
|
||||
"muntin": (232, 239, 242),
|
||||
"storage": (96, 96, 94),
|
||||
"sofa": (138, 121, 105),
|
||||
"sofa_shadow": (98, 84, 73),
|
||||
"table": (177, 138, 95),
|
||||
"desk": (83, 71, 64),
|
||||
"chair": (56, 57, 58),
|
||||
"accent": (122, 100, 78),
|
||||
"bed": (192, 175, 159),
|
||||
"headboard": (103, 84, 71),
|
||||
"linen_1": (218, 206, 196),
|
||||
"linen_2": (143, 132, 118),
|
||||
"bench": (134, 103, 80),
|
||||
"shelf": (67, 63, 61),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
draw_zoning_plan()
|
||||
draw_perspective_room("render-1-warm-nordic", PALETTES["render-1-warm-nordic"], "lounge")
|
||||
draw_perspective_room("render-2-soft-japandi", PALETTES["render-2-soft-japandi"], "bedroom")
|
||||
draw_perspective_room("render-3-rustic-studio", PALETTES["render-3-rustic-studio"], "studio")
|
||||
@@ -0,0 +1,508 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageChops, ImageDraw, ImageFilter, ImageFont
|
||||
|
||||
|
||||
ROOT = Path("/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026")
|
||||
OUT = ROOT / "za-pregled" / "warm-nordic-highres"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
||||
options = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/truetype/liberation2/LiberationSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf",
|
||||
]
|
||||
for path in options:
|
||||
try:
|
||||
return ImageFont.truetype(path, size)
|
||||
except OSError:
|
||||
continue
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
TITLE = font(66, bold=True)
|
||||
HEAD = font(38, bold=True)
|
||||
TEXT = font(28)
|
||||
SMALL = font(22)
|
||||
|
||||
|
||||
def add_noise(arr: np.ndarray, strength: float) -> np.ndarray:
|
||||
noise = np.random.normal(0, strength, arr.shape).astype(np.float32)
|
||||
out = np.clip(arr.astype(np.float32) + noise, 0, 255)
|
||||
return out.astype(np.uint8)
|
||||
|
||||
|
||||
def vertical_gradient(size: tuple[int, int], top: tuple[int, int, int], bottom: tuple[int, int, int]) -> Image.Image:
|
||||
w, h = size
|
||||
base = np.zeros((h, w, 3), dtype=np.uint8)
|
||||
for i in range(h):
|
||||
t = i / max(h - 1, 1)
|
||||
base[i, :, :] = [int(top[c] * (1 - t) + bottom[c] * t) for c in range(3)]
|
||||
return Image.fromarray(base, "RGB")
|
||||
|
||||
|
||||
def wood_texture(size: tuple[int, int], base=(194, 159, 120), dark=(125, 90, 58), direction: str = "horizontal") -> Image.Image:
|
||||
w, h = size
|
||||
axis = w if direction == "horizontal" else h
|
||||
img = np.zeros((h, w, 3), dtype=np.uint8)
|
||||
grain = np.zeros(axis, dtype=np.float32)
|
||||
phase = np.random.uniform(0, np.pi)
|
||||
for i in range(axis):
|
||||
t = i / axis
|
||||
wave = (
|
||||
0.55 * np.sin(t * 28 + phase)
|
||||
+ 0.22 * np.sin(t * 91 + 0.7)
|
||||
+ 0.18 * np.sin(t * 160 + 1.9)
|
||||
)
|
||||
grain[i] = wave
|
||||
grain += np.random.normal(0, 0.09, axis)
|
||||
grain = (grain - grain.min()) / (grain.max() - grain.min() + 1e-6)
|
||||
if direction == "horizontal":
|
||||
val = np.tile(grain, (h, 1))
|
||||
else:
|
||||
val = np.tile(grain[:, None], (1, w))
|
||||
for c in range(3):
|
||||
img[:, :, c] = (dark[c] * (1 - val) + base[c] * val).astype(np.uint8)
|
||||
img = add_noise(img, 5)
|
||||
return Image.fromarray(img, "RGB").filter(ImageFilter.GaussianBlur(0.6))
|
||||
|
||||
|
||||
def concrete_texture(size: tuple[int, int], base=(194, 190, 183)) -> Image.Image:
|
||||
w, h = size
|
||||
arr = np.full((h, w, 3), base, dtype=np.uint8)
|
||||
arr = add_noise(arr, 12)
|
||||
specks = np.random.uniform(0, 1, (h, w))
|
||||
arr[specks > 0.995] = np.clip(arr[specks > 0.995] - 30, 0, 255)
|
||||
return Image.fromarray(arr, "RGB").filter(ImageFilter.GaussianBlur(0.4))
|
||||
|
||||
|
||||
def brick_texture(size: tuple[int, int]) -> Image.Image:
|
||||
w, h = size
|
||||
img = Image.new("RGB", size, (206, 191, 177))
|
||||
draw = ImageDraw.Draw(img)
|
||||
brick_h = 42
|
||||
brick_w = 105
|
||||
shades = [(163, 111, 84), (174, 122, 91), (145, 97, 74), (186, 133, 99)]
|
||||
for row, y in enumerate(range(0, h, brick_h)):
|
||||
offset = 0 if row % 2 == 0 else brick_w // 2
|
||||
for x in range(-offset, w, brick_w):
|
||||
c = shades[(row + x // max(brick_w, 1)) % len(shades)]
|
||||
inset = 4
|
||||
draw.rounded_rectangle(
|
||||
(x + inset, y + inset, x + brick_w - 8, y + brick_h - 6),
|
||||
radius=3,
|
||||
fill=c,
|
||||
)
|
||||
return img.filter(ImageFilter.GaussianBlur(0.2))
|
||||
|
||||
|
||||
def linen_texture(size: tuple[int, int], base=(223, 213, 198), accent=(190, 177, 159)) -> Image.Image:
|
||||
w, h = size
|
||||
arr = np.zeros((h, w, 3), dtype=np.uint8)
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
mix = 0.55 + 0.25 * np.sin(x / 12.0) + 0.2 * np.sin(y / 14.0)
|
||||
mix = max(0.0, min(1.0, mix))
|
||||
arr[y, x] = [int(base[c] * mix + accent[c] * (1 - mix)) for c in range(3)]
|
||||
arr = add_noise(arr, 9)
|
||||
return Image.fromarray(arr, "RGB").filter(ImageFilter.GaussianBlur(0.5))
|
||||
|
||||
|
||||
def slat_texture(size: tuple[int, int]) -> Image.Image:
|
||||
w, h = size
|
||||
base = Image.new("RGB", size, (74, 67, 62))
|
||||
draw = ImageDraw.Draw(base)
|
||||
wood = wood_texture((46, h), base=(172, 136, 94), dark=(110, 76, 46), direction="vertical")
|
||||
for x in range(0, w, 56):
|
||||
base.paste(wood, (x + 8, 0))
|
||||
return base.filter(ImageFilter.GaussianBlur(0.3))
|
||||
|
||||
|
||||
def rug_texture(size: tuple[int, int], base=(206, 197, 184), line=(161, 150, 132)) -> Image.Image:
|
||||
img = linen_texture(size, base, line)
|
||||
draw = ImageDraw.Draw(img)
|
||||
w, h = size
|
||||
for y in range(40, h, 58):
|
||||
draw.line((40, y, w - 40, y), fill=(line[0] - 8, line[1] - 8, line[2] - 8), width=2)
|
||||
return img
|
||||
|
||||
|
||||
def repeat_texture(tex: Image.Image, size: tuple[int, int]) -> Image.Image:
|
||||
w, h = size
|
||||
tw, th = tex.size
|
||||
out = Image.new("RGB", size)
|
||||
for y in range(0, h, th):
|
||||
for x in range(0, w, tw):
|
||||
out.paste(tex, (x, y))
|
||||
return out
|
||||
|
||||
|
||||
def find_perspective_coeffs(dst_pts, src_pts):
|
||||
matrix = []
|
||||
for (x, y), (u, v) in zip(dst_pts, src_pts):
|
||||
matrix.append([x, y, 1, 0, 0, 0, -u * x, -u * y])
|
||||
matrix.append([0, 0, 0, x, y, 1, -v * x, -v * y])
|
||||
a = np.array(matrix, dtype=np.float64)
|
||||
b = np.array(src_pts).reshape(8)
|
||||
coeffs = np.linalg.solve(a, b)
|
||||
return coeffs
|
||||
|
||||
|
||||
def polygon_mask(size: tuple[int, int], pts: list[tuple[int, int]]) -> Image.Image:
|
||||
mask = Image.new("L", size, 0)
|
||||
ImageDraw.Draw(mask).polygon(pts, fill=255)
|
||||
return mask
|
||||
|
||||
|
||||
def warp_texture_to_polygon(tex: Image.Image, polygon: list[tuple[int, int]], canvas_size: tuple[int, int]) -> tuple[Image.Image, Image.Image]:
|
||||
xs = [p[0] for p in polygon]
|
||||
ys = [p[1] for p in polygon]
|
||||
min_x, max_x = min(xs), max(xs)
|
||||
min_y, max_y = min(ys), max(ys)
|
||||
bw = max_x - min_x
|
||||
bh = max_y - min_y
|
||||
local = [(x - min_x, y - min_y) for x, y in polygon]
|
||||
src = [(0, 0), (tex.size[0], 0), (tex.size[0], tex.size[1]), (0, tex.size[1])]
|
||||
coeffs = find_perspective_coeffs(local, src)
|
||||
tiled = repeat_texture(tex, (bw + 20, bh + 20))
|
||||
warped = tiled.transform((bw, bh), Image.PERSPECTIVE, coeffs, Image.Resampling.BICUBIC)
|
||||
layer = Image.new("RGBA", canvas_size, (0, 0, 0, 0))
|
||||
mask = polygon_mask((bw, bh), local)
|
||||
layer.paste(warped.convert("RGBA"), (min_x, min_y), mask)
|
||||
canvas_mask = Image.new("L", canvas_size, 0)
|
||||
canvas_mask.paste(mask, (min_x, min_y))
|
||||
return layer, canvas_mask
|
||||
|
||||
|
||||
def paste_polygon_texture(base: Image.Image, polygon: list[tuple[int, int]], tex: Image.Image, tint: tuple[int, int, int] | None = None, alpha: int = 255) -> None:
|
||||
layer, mask = warp_texture_to_polygon(tex, polygon, base.size)
|
||||
if tint is not None:
|
||||
tint_img = Image.new("RGBA", base.size, (*tint, 0))
|
||||
tint_img.putalpha(mask.point(lambda p: int(p * alpha / 255)))
|
||||
layer = Image.blend(layer, tint_img, 0.18)
|
||||
base.alpha_composite(layer)
|
||||
|
||||
|
||||
def add_polygon_overlay(base: Image.Image, polygon: list[tuple[int, int]], color: tuple[int, int, int], opacity: int) -> None:
|
||||
overlay = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
draw.polygon(polygon, fill=(*color, opacity))
|
||||
base.alpha_composite(overlay)
|
||||
|
||||
|
||||
def add_shadow(base: Image.Image, shape_box: tuple[int, int, int, int], offset=(18, 18), blur=24, opacity=80, radius=30) -> None:
|
||||
shadow = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(shadow)
|
||||
x1, y1, x2, y2 = shape_box
|
||||
draw.rounded_rectangle((x1 + offset[0], y1 + offset[1], x2 + offset[0], y2 + offset[1]), radius=radius, fill=(25, 24, 22, opacity))
|
||||
shadow = shadow.filter(ImageFilter.GaussianBlur(blur))
|
||||
base.alpha_composite(shadow)
|
||||
|
||||
|
||||
def rounded_box(base: Image.Image, box, fill, outline=None, width=1, radius=28, texture: Image.Image | None = None) -> None:
|
||||
if texture is not None:
|
||||
mask = Image.new("L", base.size, 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle(box, radius=radius, fill=255)
|
||||
tex = repeat_texture(texture, base.size).convert("RGBA")
|
||||
tex.putalpha(mask)
|
||||
base.alpha_composite(tex)
|
||||
overlay = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)
|
||||
base.alpha_composite(overlay)
|
||||
|
||||
|
||||
def add_text_panel(base: Image.Image, title: str, subtitle: str) -> None:
|
||||
panel = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(panel)
|
||||
draw.rounded_rectangle((56, 48, 1030, 220), radius=30, fill=(244, 239, 230, 220))
|
||||
draw.text((92, 78), title, font=HEAD, fill=(34, 34, 34))
|
||||
draw.text((94, 138), subtitle, font=TEXT, fill=(82, 76, 68))
|
||||
base.alpha_composite(panel)
|
||||
|
||||
|
||||
def draw_lounge_render() -> None:
|
||||
size = (3200, 1800)
|
||||
base = vertical_gradient(size, (243, 234, 223), (226, 214, 199)).convert("RGBA")
|
||||
|
||||
left_roof = [(100, 450), (860, 170), (1180, 980), (420, 1270)]
|
||||
right_roof = [(2140, 170), (3100, 450), (2780, 1270), (2020, 980)]
|
||||
back_wall = [(860, 170), (2140, 170), (2560, 450), (2080, 980), (1180, 980), (640, 450)]
|
||||
floor = [(420, 1270), (2780, 1270), (2080, 980), (1180, 980)]
|
||||
|
||||
paste_polygon_texture(base, floor, wood_texture((1200, 900), base=(152, 118, 84), dark=(98, 70, 44), direction="horizontal"))
|
||||
add_polygon_overlay(base, floor, (40, 24, 10), 24)
|
||||
paste_polygon_texture(base, back_wall, brick_texture((1400, 900)))
|
||||
add_polygon_overlay(base, back_wall, (86, 67, 48), 18)
|
||||
paste_polygon_texture(base, left_roof, wood_texture((1600, 1200), base=(215, 184, 147), dark=(160, 122, 84), direction="vertical"))
|
||||
paste_polygon_texture(base, right_roof, wood_texture((1600, 1200), base=(215, 184, 147), dark=(160, 122, 84), direction="vertical"))
|
||||
add_polygon_overlay(base, left_roof, (75, 50, 25), 16)
|
||||
add_polygon_overlay(base, right_roof, (75, 50, 25), 26)
|
||||
|
||||
beam = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(beam)
|
||||
draw.line((840, 200, 2160, 200), fill=(116, 73, 44, 255), width=30)
|
||||
for x in [660, 920, 1180, 1440, 1700, 1960, 2220, 2480]:
|
||||
draw.line((x, 280, x + 150, 930), fill=(124, 80, 50, 255), width=24)
|
||||
base.alpha_composite(beam)
|
||||
|
||||
light = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(light)
|
||||
draw.polygon([(640, 380), (1020, 250), (1060, 1270), (560, 1270)], fill=(255, 243, 214, 38))
|
||||
draw.polygon([(0, 820), (520, 710), (1600, 1770), (0, 1770)], fill=(255, 218, 154, 36))
|
||||
base.alpha_composite(light.filter(ImageFilter.GaussianBlur(35)))
|
||||
|
||||
wall_overlay = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dwo = ImageDraw.Draw(wall_overlay)
|
||||
dwo.rounded_rectangle((860, 330, 1120, 930), radius=12, fill=(140, 97, 60, 255), outline=(104, 68, 41, 255), width=5)
|
||||
dwo.rectangle((885, 355, 1090, 900), fill=(181, 208, 223, 255))
|
||||
dwo.line((988, 355, 988, 900), fill=(235, 241, 245, 255), width=5)
|
||||
dwo.rounded_rectangle((1360, 325, 1600, 935), radius=12, fill=(138, 95, 58, 255), outline=(100, 66, 39, 255), width=5)
|
||||
dwo.rectangle((1385, 350, 1570, 905), fill=(189, 209, 220, 255))
|
||||
dwo.rounded_rectangle((1640, 345, 1870, 910), radius=12, fill=(138, 95, 58, 255), outline=(100, 66, 39, 255), width=5)
|
||||
dwo.rectangle((1664, 372, 1844, 880), fill=(192, 212, 222, 255))
|
||||
base.alpha_composite(wall_overlay)
|
||||
|
||||
# Central storage / media core
|
||||
core_front = [(1730, 575), (2070, 575), (2070, 1210), (1730, 1210)]
|
||||
core_side = [(2070, 575), (2285, 685), (2285, 1325), (2070, 1210)]
|
||||
paste_polygon_texture(base, core_front, slat_texture((600, 900)))
|
||||
paste_polygon_texture(base, core_side, wood_texture((500, 900), base=(183, 150, 113), dark=(118, 87, 58), direction="vertical"))
|
||||
add_polygon_overlay(base, core_front, (46, 38, 30), 22)
|
||||
add_polygon_overlay(base, core_side, (38, 30, 24), 28)
|
||||
|
||||
storage_draw = ImageDraw.Draw(base)
|
||||
storage_draw.rounded_rectangle((1770, 660, 2030, 790), radius=12, fill=(28, 28, 30, 255))
|
||||
storage_draw.rounded_rectangle((1788, 835, 2015, 960), radius=16, fill=(133, 98, 64, 255))
|
||||
storage_draw.rectangle((1810, 870, 1992, 908), fill=(70, 56, 45, 255))
|
||||
storage_draw.rounded_rectangle((1768, 1010, 2017, 1165), radius=18, outline=(206, 181, 148, 180), width=3)
|
||||
storage_draw.line((2190, 745, 2190, 1255), fill=(86, 64, 45, 200), width=3)
|
||||
|
||||
# TV, floating media shelf
|
||||
rounded_box(base, (1835, 680, 2010, 775), (18, 18, 20, 255), outline=(42, 42, 42, 255), width=4, radius=10)
|
||||
rounded_box(base, (1815, 805, 2025, 845), (126, 93, 63, 220), radius=10)
|
||||
|
||||
# Coffee nook
|
||||
add_shadow(base, (920, 935, 1275, 1210), blur=20, opacity=70, radius=24)
|
||||
rounded_box(base, (920, 935, 1275, 1210), (142, 109, 77, 255), texture=wood_texture((400, 400), base=(162, 126, 92), dark=(109, 79, 53), direction="vertical"))
|
||||
top = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
ImageDraw.Draw(top).rounded_rectangle((900, 905, 1295, 965), radius=18, fill=(230, 224, 212, 255))
|
||||
base.alpha_composite(top)
|
||||
coffee = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dc = ImageDraw.Draw(coffee)
|
||||
dc.rectangle((987, 820, 1180, 900), fill=(188, 170, 142, 220))
|
||||
dc.rectangle((1055, 760, 1115, 820), fill=(230, 230, 228, 255))
|
||||
dc.ellipse((1070, 730, 1110, 770), fill=(250, 250, 250, 255))
|
||||
dc.rounded_rectangle((1210, 855, 1260, 905), radius=10, fill=(72, 72, 72, 255))
|
||||
base.alpha_composite(coffee)
|
||||
|
||||
# Sofa and rug
|
||||
add_shadow(base, (1090, 1110, 1730, 1445), blur=32, opacity=80, radius=46)
|
||||
rounded_box(base, (1010, 1135, 1760, 1450), (196, 181, 163, 235), texture=linen_texture((900, 500), base=(203, 189, 171), accent=(173, 158, 140)))
|
||||
rounded_box(base, (1010, 1055, 1450, 1205), (205, 193, 178, 245), texture=linen_texture((600, 260), base=(209, 196, 180), accent=(185, 170, 151)))
|
||||
rounded_box(base, (1350, 1015, 1745, 1185), (202, 189, 174, 245), texture=linen_texture((550, 260), base=(206, 193, 177), accent=(181, 166, 149)))
|
||||
add_shadow(base, (1115, 1270, 1615, 1530), blur=25, opacity=60, radius=60)
|
||||
rounded_box(base, (1125, 1280, 1625, 1525), (235, 227, 214, 215), texture=rug_texture((800, 420)))
|
||||
add_shadow(base, (1290, 1180, 1510, 1350), blur=20, opacity=55, radius=80)
|
||||
rounded_box(base, (1295, 1188, 1515, 1348), (188, 153, 112, 235), texture=wood_texture((300, 220), base=(192, 157, 114), dark=(128, 95, 62), direction="horizontal"), radius=90)
|
||||
|
||||
# Sideboard under slope
|
||||
rounded_box(base, (2440, 980, 2890, 1135), (173, 154, 137, 220), texture=wood_texture((600, 220), base=(177, 152, 124), dark=(119, 94, 68), direction="horizontal"))
|
||||
deco = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dd = ImageDraw.Draw(deco)
|
||||
dd.ellipse((2480, 880, 2570, 1035), fill=(104, 124, 88, 255))
|
||||
dd.rectangle((2515, 975, 2535, 1035), fill=(122, 94, 70, 255))
|
||||
dd.ellipse((2605, 905, 2660, 990), fill=(205, 186, 166, 255))
|
||||
dd.ellipse((2690, 912, 2745, 995), fill=(222, 214, 202, 255))
|
||||
base.alpha_composite(deco)
|
||||
|
||||
add_text_panel(base, "Warm Nordic Lounge", "Terrace side: living room, TV wall on storage core, integrated coffee nook.")
|
||||
|
||||
draw = ImageDraw.Draw(base)
|
||||
draw.text((74, 1660), "Storage koncept: hrastove letvice oko centralnog volumena skrivaju posteljinu, kofer, sezonske stvari i utility zonu.", font=SMALL, fill=(58, 53, 47))
|
||||
|
||||
base = base.filter(ImageFilter.UnsharpMask(radius=2, percent=130, threshold=2))
|
||||
base.save(OUT / "warm-nordic-lounge-render.png", quality=95)
|
||||
|
||||
|
||||
def draw_studio_render() -> None:
|
||||
size = (3200, 1800)
|
||||
base = vertical_gradient(size, (239, 233, 224), (219, 209, 196)).convert("RGBA")
|
||||
|
||||
left_roof = [(90, 420), (880, 160), (1220, 980), (380, 1290)]
|
||||
right_roof = [(2060, 160), (3110, 420), (2820, 1290), (1980, 980)]
|
||||
back_wall = [(880, 160), (2060, 160), (2520, 420), (2040, 980), (1220, 980), (640, 420)]
|
||||
floor = [(380, 1290), (2820, 1290), (2040, 980), (1220, 980)]
|
||||
|
||||
paste_polygon_texture(base, floor, wood_texture((1200, 900), base=(148, 115, 84), dark=(92, 67, 45), direction="horizontal"))
|
||||
add_polygon_overlay(base, floor, (30, 18, 8), 34)
|
||||
paste_polygon_texture(base, back_wall, brick_texture((1400, 900)))
|
||||
add_polygon_overlay(base, back_wall, (74, 56, 40), 30)
|
||||
paste_polygon_texture(base, left_roof, wood_texture((1600, 1200), base=(209, 177, 139), dark=(149, 111, 76), direction="vertical"))
|
||||
paste_polygon_texture(base, right_roof, wood_texture((1600, 1200), base=(209, 177, 139), dark=(149, 111, 76), direction="vertical"))
|
||||
add_polygon_overlay(base, left_roof, (68, 48, 28), 22)
|
||||
add_polygon_overlay(base, right_roof, (50, 34, 18), 35)
|
||||
|
||||
beam = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(beam)
|
||||
draw.line((860, 200, 2100, 200), fill=(96, 60, 38, 255), width=28)
|
||||
for x in [690, 965, 1240, 1515, 1790, 2065, 2340, 2615]:
|
||||
draw.line((x, 260, x - 160, 960), fill=(105, 68, 44, 255), width=23)
|
||||
base.alpha_composite(beam)
|
||||
|
||||
win = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dw = ImageDraw.Draw(win)
|
||||
for box in [(1180, 325, 1400, 930), (1570, 345, 1795, 920)]:
|
||||
dw.rounded_rectangle(box, radius=10, fill=(137, 98, 61, 255), outline=(103, 67, 39, 255), width=5)
|
||||
x1, y1, x2, y2 = box
|
||||
dw.rectangle((x1 + 24, y1 + 24, x2 - 24, y2 - 24), fill=(190, 210, 222, 255))
|
||||
dw.line(((x1 + x2) // 2, y1 + 24, (x1 + x2) // 2, y2 - 24), fill=(234, 240, 244, 255), width=4)
|
||||
base.alpha_composite(win)
|
||||
|
||||
light = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dl = ImageDraw.Draw(light)
|
||||
dl.polygon([(1115, 380), (1835, 395), (1920, 1450), (1020, 1450)], fill=(255, 236, 205, 32))
|
||||
base.alpha_composite(light.filter(ImageFilter.GaussianBlur(40)))
|
||||
|
||||
# Storage / acoustic core
|
||||
core_front = [(930, 560), (1320, 560), (1320, 1240), (930, 1240)]
|
||||
core_side = [(1320, 560), (1545, 675), (1545, 1358), (1320, 1240)]
|
||||
paste_polygon_texture(base, core_front, slat_texture((650, 950)))
|
||||
paste_polygon_texture(base, core_side, wood_texture((400, 950), base=(176, 145, 110), dark=(112, 80, 54), direction="vertical"))
|
||||
add_polygon_overlay(base, core_front, (38, 33, 29), 28)
|
||||
add_polygon_overlay(base, core_side, (28, 22, 18), 22)
|
||||
|
||||
drawc = ImageDraw.Draw(base)
|
||||
drawc.rounded_rectangle((980, 675, 1268, 1010), radius=18, fill=(160, 128, 92, 100), outline=(220, 192, 152, 180), width=3)
|
||||
drawc.rounded_rectangle((1000, 1035, 1248, 1180), radius=18, outline=(220, 192, 152, 180), width=3)
|
||||
drawc.rounded_rectangle((1360, 760, 1508, 900), radius=12, fill=(42, 42, 44, 255))
|
||||
|
||||
# Studio desk
|
||||
add_shadow(base, (1580, 990, 2500, 1235), blur=24, opacity=82, radius=24)
|
||||
rounded_box(base, (1580, 990, 2500, 1235), (120, 88, 61, 240), texture=wood_texture((1000, 320), base=(140, 105, 74), dark=(91, 64, 41), direction="horizontal"), radius=16)
|
||||
desk = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
dd = ImageDraw.Draw(desk)
|
||||
dd.rounded_rectangle((1755, 830, 2005, 980), radius=16, fill=(28, 28, 30, 255))
|
||||
dd.rounded_rectangle((2040, 830, 2295, 980), radius=16, fill=(28, 28, 30, 255))
|
||||
dd.rectangle((1715, 905, 2340, 930), fill=(38, 38, 40, 255))
|
||||
dd.rectangle((1720, 780, 1785, 990), fill=(45, 45, 47, 255))
|
||||
dd.rectangle((2260, 780, 2325, 990), fill=(45, 45, 47, 255))
|
||||
dd.rectangle((1910, 870, 2050, 955), fill=(231, 225, 214, 255))
|
||||
dd.rectangle((2060, 1040, 2125, 1235), fill=(53, 53, 55, 255))
|
||||
base.alpha_composite(desk)
|
||||
|
||||
# Lounge daybed
|
||||
add_shadow(base, (430, 1090, 1220, 1435), blur=28, opacity=72, radius=40)
|
||||
rounded_box(base, (430, 1105, 1220, 1435), (191, 178, 161, 235), texture=linen_texture((900, 400), base=(198, 186, 170), accent=(166, 151, 135)), radius=34)
|
||||
rounded_box(base, (465, 1045, 1150, 1175), (202, 190, 175, 245), texture=linen_texture((760, 180), base=(205, 194, 179), accent=(172, 157, 141)), radius=28)
|
||||
rounded_box(base, (615, 1240, 900, 1380), (145, 110, 84, 235), texture=linen_texture((360, 180), base=(150, 118, 93), accent=(120, 93, 71)), radius=22)
|
||||
|
||||
# Acoustic wall and guitars
|
||||
paste_polygon_texture(base, [(2530, 520), (2850, 635), (2850, 1160), (2530, 1060)], slat_texture((500, 700)))
|
||||
acc = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||
da = ImageDraw.Draw(acc)
|
||||
da.line((2670, 870, 2670, 1160), fill=(99, 74, 52, 255), width=10)
|
||||
da.ellipse((2630, 785, 2710, 905), outline=(148, 113, 76, 255), width=8)
|
||||
da.line((2760, 845, 2760, 1140), fill=(99, 74, 52, 255), width=10)
|
||||
da.ellipse((2720, 760, 2800, 880), outline=(148, 113, 76, 255), width=8)
|
||||
base.alpha_composite(acc)
|
||||
|
||||
# Low storage under slope
|
||||
rounded_box(base, (2330, 1080, 2900, 1220), (173, 155, 137, 220), texture=wood_texture((620, 220), base=(178, 151, 123), dark=(118, 93, 67), direction="horizontal"), radius=20)
|
||||
|
||||
# Rug
|
||||
add_shadow(base, (1520, 1200, 2450, 1490), blur=20, opacity=50, radius=40)
|
||||
rounded_box(base, (1540, 1210, 2460, 1490), (227, 221, 210, 220), texture=rug_texture((1000, 320), base=(221, 213, 201), line=(175, 162, 145)), radius=28)
|
||||
|
||||
add_text_panel(base, "Warm Nordic Home Studio", "Stair side: recording desk, acoustic slat storage spine, lounge/daybed for listening sessions.")
|
||||
draw = ImageDraw.Draw(base)
|
||||
draw.text((74, 1660), "Storage spine: zatvoreni ormari gore za kabaste stvari, otvorene nishe za knjige/ploca, utility rack prema studiju.", font=SMALL, fill=(58, 53, 47))
|
||||
|
||||
base = base.filter(ImageFilter.UnsharpMask(radius=2, percent=130, threshold=2))
|
||||
base.save(OUT / "warm-nordic-studio-render.png", quality=95)
|
||||
|
||||
|
||||
def draw_layout_plan() -> None:
|
||||
size = (2600, 1800)
|
||||
base = Image.new("RGBA", size, (244, 240, 234, 255))
|
||||
draw = ImageDraw.Draw(base)
|
||||
draw.rounded_rectangle((160, 160, 2440, 1640), radius=24, fill=(248, 245, 241, 255), outline=(66, 60, 55, 255), width=6)
|
||||
draw.rectangle((300, 1220, 760, 1540), fill=(238, 236, 232, 255), outline=(140, 90, 90, 255), width=5)
|
||||
draw.text((362, 1350), "Stepenice", font=HEAD, fill=(124, 55, 55))
|
||||
draw.rectangle((1040, 460, 1510, 1320), fill=(186, 156, 120, 255), outline=(110, 84, 58, 255), width=5)
|
||||
draw.text((1080, 780), "Storage\ncore", font=TITLE, fill=(60, 40, 26))
|
||||
|
||||
# lounge zone
|
||||
draw.rounded_rectangle((250, 250, 980, 1070), radius=26, fill=(230, 214, 195, 255), outline=(150, 123, 94, 255), width=4)
|
||||
draw.text((390, 310), "Living / Lounge", font=TITLE, fill=(82, 58, 39))
|
||||
draw.rounded_rectangle((330, 655, 740, 820), radius=30, fill=(197, 183, 166, 255))
|
||||
draw.rounded_rectangle((565, 875, 790, 995), radius=60, fill=(190, 153, 112, 255))
|
||||
draw.rectangle((790, 640, 950, 760), fill=(28, 28, 30, 255))
|
||||
draw.rectangle((300, 905, 500, 1025), fill=(145, 110, 80, 255))
|
||||
draw.text((300, 1045), "coffee nook", font=TEXT, fill=(90, 65, 45))
|
||||
draw.text((750, 785), "TV", font=TEXT, fill=(240, 240, 240))
|
||||
|
||||
# studio zone
|
||||
draw.rounded_rectangle((1560, 250, 2320, 1440), radius=26, fill=(220, 227, 221, 255), outline=(109, 128, 112, 255), width=4)
|
||||
draw.text((1720, 310), "Music Studio", font=TITLE, fill=(57, 80, 65))
|
||||
draw.rectangle((1680, 650, 2120, 790), fill=(121, 88, 63, 255))
|
||||
draw.rectangle((1760, 520, 1885, 630), fill=(28, 28, 30, 255))
|
||||
draw.rectangle((1925, 520, 2050, 630), fill=(28, 28, 30, 255))
|
||||
draw.rounded_rectangle((1680, 1030, 2150, 1210), radius=26, fill=(194, 181, 165, 255))
|
||||
draw.text((1685, 800), "recording desk", font=TEXT, fill=(49, 40, 33))
|
||||
draw.text((1710, 1225), "listening lounge / daybed", font=TEXT, fill=(67, 80, 72))
|
||||
|
||||
# storage logic
|
||||
draw.rounded_rectangle((930, 250, 1620, 390), radius=22, fill=(216, 204, 186, 255), outline=(147, 126, 104, 255), width=3)
|
||||
draw.text((960, 295), "gornji zatvoreni storage za posteljinu, kofere, sezonske stvari", font=TEXT, fill=(69, 56, 43))
|
||||
draw.rounded_rectangle((930, 1380, 1620, 1520), radius=22, fill=(216, 204, 186, 255), outline=(147, 126, 104, 255), width=3)
|
||||
draw.text((995, 1425), "nise / utility / skriveni kablovi / rack", font=TEXT, fill=(69, 56, 43))
|
||||
|
||||
draw.text((170, 70), "Warm Nordic Loft - finalni koncept rasporeda", font=TITLE, fill=(28, 28, 28))
|
||||
draw.text((170, 1700), "Centralni storage volumen resava stubove i odzak kao dizajn feature, a ne kao smetnju.", font=TEXT, fill=(44, 44, 44))
|
||||
base.save(OUT / "warm-nordic-layout-plan.png", quality=95)
|
||||
|
||||
|
||||
def write_concept_note() -> None:
|
||||
note = """# Warm Nordic Lounge + Studio
|
||||
|
||||
## Finalni pravac
|
||||
- terasa strana: living room / lounge
|
||||
- TV je vezan za centralni storage volumen
|
||||
- coffee making kutak je uz lounge zonu, blizu terase
|
||||
- stepenice strana: home music recording studio + listening lounge
|
||||
- izmedju: storage spine oko stubova i odzaka
|
||||
|
||||
## Kako storage poboljsava ambijent
|
||||
- obloziti centralni volumen hrastovim vertikalnim letvicama ili finim furnirom
|
||||
- gornje fronte zatvorene i mirne, push-open, bez vizuelnog nereda
|
||||
- po jedna otvorena nisa prema svakoj zoni za dekor, knjige, ploce ili kafe set
|
||||
- TV strana ostaje cista i smirena
|
||||
- studio strana dobija integrisanu akusticku logiku i skriveni kablovski management
|
||||
|
||||
## Materijali
|
||||
- svetliji hrast / smoked oak u toplom tonu
|
||||
- opeka ostaje vidljiva i ociscena
|
||||
- plafonska drvena obloga prirodan mat ton
|
||||
- tekstil: lan, greige, pesak, maslina
|
||||
- metalni detalji: crni ili tamno bronzani
|
||||
|
||||
## Napomena
|
||||
Ovi vizuali su high-res textured concept renders, ne pravi V-Ray / Corona / AI photo renders.
|
||||
Za potpuno fotorealistican rezultat na tacnoj geometriji treba ili:
|
||||
1. originalne fotografije kao lokalni fajlovi za photomontage, ili
|
||||
2. 3D model u SketchUp / Blender + render engine.
|
||||
"""
|
||||
(OUT / "warm-nordic-koncept.md").write_text(note)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
draw_layout_plan()
|
||||
draw_lounge_render()
|
||||
draw_studio_render()
|
||||
write_concept_note()
|
||||
@@ -0,0 +1,162 @@
|
||||
# Potkrovlje - izolacija krova i koncept uredjenja
|
||||
|
||||
Datum provere cena: 2026-03-25
|
||||
|
||||
## 1. Sta sam uzeo kao bazu
|
||||
- Bruto povrsina iz dostavljene tabele: `79.29 m2`
|
||||
- Povrsina terase iz tabele: `5.89 m2`
|
||||
- Za krov sam radio dve varijante:
|
||||
- ako terasa nije pod krovom: `76.92 m2` kosina
|
||||
- ako puna bruto projekcija ulazi u krov: `83.09 m2` kosina
|
||||
- Za nabavku sam kao realan radni scenario uzeo:
|
||||
- `84.61 m2` sa 10% rezerve u varijanti bez terase pod krovom
|
||||
- gornji bezbedni scenario: `91.40 m2` sa 10% rezerve
|
||||
|
||||
## 2. Moja tehnicka preporuka
|
||||
|
||||
### Preporuceni sistem
|
||||
Od spolja ka unutra:
|
||||
1. postojeci crep
|
||||
2. postojeca paropropusna folija
|
||||
3. postojece daske
|
||||
4. proverena ventilisana distanca 3-5 cm ako membrana nije sigurno potvrdena kao ispravna i dovoljno difuzno otvorena
|
||||
5. 10 cm kamena vuna izmedju rogova
|
||||
6. dodatnih 5 cm ili 10 cm ispod rogova u kontinuitetu
|
||||
7. unutrasnja aktivna parna brana / pametna membrana ili bar kvalitetna parna brana sa zadihtovanim spojnicama
|
||||
8. podkonstrukcija za instalacioni sloj
|
||||
9. drvena obloga - lamperija ili brodski pod, ne gips
|
||||
|
||||
### Sta je najpametnije za tvoj slucaj
|
||||
- Ako hoces balans cena / trajnost / zvuk / protivpozarno: `20 cm ukupno` mineralne vune i drvena obloga.
|
||||
- Ako hoces da ustedis, ali da i dalje bude korektno: `15 cm ukupno`.
|
||||
- Ako membrana ispod crepa nije proverena ili je starija i nepoznata, ja ne bih nabijao vunu direktno u daske bez kontrole detalja.
|
||||
|
||||
### Ocekivani termicki nivo
|
||||
- `15 cm` mineralne vune: okvirno `U oko 0.23-0.26 W/m2K`
|
||||
- `20 cm` mineralne vune: okvirno `U oko 0.18-0.21 W/m2K`
|
||||
|
||||
Zakljucak: za potkrovlje koje zelis da koristis kao ozbiljan stambeni prostor, ja bih isao na `20 cm`.
|
||||
|
||||
## 3. Kvadratura i kolicine
|
||||
|
||||
### Radna nabavna kvadratura
|
||||
- Preporuka za obracun i porucivanje: `85 m2`
|
||||
- Ako zelis potpuno bezbednu gornju rezervu: `91-92 m2`
|
||||
|
||||
### Zapremina izolacije
|
||||
- Za `15 cm`: oko `12.69 m3` izolacije sa rezervom
|
||||
- Za `20 cm`: oko `16.92 m3` izolacije sa rezervom
|
||||
|
||||
## 4. Cene sa neta i kako sam ih citao
|
||||
|
||||
### Izolacija i membrane
|
||||
- `NaturBoard FIT Plus Knauf` na Roma: `od 436 RSD/kom`, proizvod za kosi krov, kamena vuna. Na Knauf oficijelnom listu za varijantu 100 mm stoji ploca `600 x 1000 mm`, 6 kom u paketu = `3.60 m2/pak`. Iz toga izlazi maloprodajni reper od oko `727 RSD/m2` za `10 cm`.
|
||||
- `KNAUF PARNA BRANA LDS 5 Silk 75m2` na Roma: `10,030 RSD/rolna`
|
||||
- `KNAUF PARNA BRANA LDS 200 AluPlus 75m2` na Roma: `8,685 RSD/rolna`
|
||||
- `Knauf HomeSeal LDS Solifit 1 25m` na Roma: `2,050 RSD/rolna`
|
||||
|
||||
### Drvena podkonstrukcija i obloga
|
||||
- `Letva 3x5 3m` na Crafter: `259 RSD/kom`
|
||||
- `Lamperija severna jela` na Krajina drvo / KP: `1,370 RSD/m2`
|
||||
- `Camova lamperija 10 mm` na Krajina drvo: `1,000 RSD/m2`
|
||||
- Premium sauna omorika na Aquaplan: `460 RSD/m duzni`, a za `1 m2` treba `10.52 m duznih`, sto izadje oko `4,839 RSD/m2`
|
||||
|
||||
### Ruke - reperi sa trzista
|
||||
- Daibau za izolaciju potkrovlja daje vise stavki po m2; za `NaturBoard FIT PLUS` prikaz je `5.30-7.90 EUR/m2`, a za `Homeseal LDS 0.02` oko `0.80 EUR/m2`
|
||||
- Svezaskele navodi prosecnu cenu izolacije potkrovlja `7-12 EUR/m2 plus materijal`
|
||||
|
||||
### Sta iz ovoga zakljucujem
|
||||
- Cene materijala za Srbiju koje sam nasao online podrzavaju realan raspon:
|
||||
- izolacija `15 cm`: oko `1,100-1,300 RSD/m2`
|
||||
- izolacija `20 cm`: oko `1,450-1,700 RSD/m2`
|
||||
- kvalitetna unutrasnja membrana: oko `135-200 RSD/m2` po rolnama, ali sa realnim nabavnim otpadom racunaj vise
|
||||
- drvena obloga: realno `1,000-1,370 RSD/m2` za normalnu enterijersku lamperiju
|
||||
|
||||
## 5. Troskovnik koji bih ja koristio za odluku
|
||||
|
||||
Napomena: ovo je `procena`, ne ponuda izvodjaca. U total nisam stavljao teske opravke krova, zamenu trulih dasaka, novu foliju spolja, skelu spolja ili dekorativnu rasvetu.
|
||||
|
||||
### Varijanta A - korektna i racionalna, 15 cm
|
||||
- Obracunska povrsina: `84.61 m2`
|
||||
- Materijal okvirno: `288,000-311,000 RSD`
|
||||
- Ruke okvirno: `129,000-179,000 RSD`
|
||||
- Ukupno okvirno: `418,000-490,000 RSD`
|
||||
|
||||
Sta dobijas:
|
||||
- 10 cm izmedju rogova + 5 cm dodatni sloj
|
||||
- parna brana / aktivna membrana
|
||||
- podkonstrukcija
|
||||
- drvena lamperija kao final
|
||||
|
||||
### Varijanta B - moja preporuka, 20 cm
|
||||
- Obracunska povrsina: `84.61 m2`
|
||||
- Materijal okvirno: `354,000-378,000 RSD`
|
||||
- Ruke okvirno: `148,000-208,000 RSD`
|
||||
- Ukupno okvirno: `502,000-586,000 RSD`
|
||||
|
||||
Sta dobijas:
|
||||
- 20 cm ukupne izolacije
|
||||
- bolji zimski komfor i bolju zastitu leti
|
||||
- manji rizik da kasnije pozelis jos jedan dodatni sloj
|
||||
|
||||
### Gornji scenario ako se na licu mesta ispostavi da treba racunati punih 91-92 m2
|
||||
- 15 cm: oko `447,000-524,000 RSD`
|
||||
- 20 cm: oko `535,000-625,000 RSD`
|
||||
|
||||
### Moj savet za budzet
|
||||
- Ako zelis da uradis jednom i mirno: planiraj `550,000-620,000 RSD` za preporucenu varijantu 20 cm.
|
||||
- Ako zelis da ne preteras, ali da bude dobro: planiraj `440,000-500,000 RSD` za 15 cm.
|
||||
- Obe varijante dodatno zastiti sa jos `5-10%` rezerve za nepredvidjene detalje oko terasnih vrata, prodora, stepenisnog otvora i eventualnih krivih rogova.
|
||||
|
||||
## 6. Dizajn preporuka za samo potkrovlje
|
||||
|
||||
## Glavna ideja koju preporucujem
|
||||
`Warm Nordic Loft` je najbolja osnova za tvoj prostor jer vec imas:
|
||||
- lepu opeku na zabatima
|
||||
- vidljivu drvenu krovnu konstrukciju kao karakter
|
||||
- dobru prirodnu svetlost i pogled
|
||||
|
||||
Zato ne bih pravio "teski" rustik niti hladan minimalizam. Isao bih na:
|
||||
- svetlu drvenu oblogu plafona u mat ulju
|
||||
- ociscenu i prelakiranu opeku ili blago fugovanje, ne potpuno zatvaranje
|
||||
- pod u srednje toplom hrastu ili kvalitetnom SPC/engineered oak tonu
|
||||
- niske plakare pod obe kosine, dubina `45-60 cm`
|
||||
- crna ili tamno bronzana rasveta po slemenu i na zabatima
|
||||
- tekstil u toplim zemljanim tonovima: pesak, maslina, glina, siva lanena
|
||||
|
||||
## Predlog rasporeda
|
||||
- Zona uz terasu i pogled: lounge + citaonica + mali radni sto
|
||||
- Mirniji kraj uz dva prozora: spavaca zona
|
||||
- Pod kosinama sa obe strane: neprekinuti niski plakari
|
||||
- Oko stepenisnog otvora: lagana ograda od drveta i metala, ne puna zidna pregrada
|
||||
|
||||
## 7. Lokalni koncept renderi i skice
|
||||
|
||||
Napravio sam lokalne koncept-vizuale u folderu `renders/`. To nisu fotorealisticki renderi iz SketchUp/3ds Max merenog modela, nego namerno ciste koncept ilustracije bazirane na tvojoj osnovi i fotografijama, da mozemo brzo da zakljucamo pravac.
|
||||
|
||||
Fajlovi:
|
||||
- `renders/contact-sheet.png`
|
||||
- `renders/zoning-plan.png`
|
||||
- `renders/render-1-warm-nordic.png`
|
||||
- `renders/render-2-soft-japandi.png`
|
||||
- `renders/render-3-rustic-studio.png`
|
||||
|
||||
## 8. Sta bih ja uradio prvo na terenu
|
||||
1. proverio stanje folije i dasaka ispod crepa
|
||||
2. izmerio stvarnu dubinu rogova i raster
|
||||
3. proverio gde tacno moze da ide ventilacioni kanal
|
||||
4. odlucio da li zadrzavas potpuno otvoren loft ili hoces pregradu za spavaci deo
|
||||
5. tek onda zatvorio finalnu debljinu izolacije i tacnu nabavnu listu
|
||||
|
||||
## 9. Izvori
|
||||
- Knauf roof insulation: https://www.knaufinsulation.com/what-we-do/our-solutions/mineral-wool-insulation-for-buildings/roof-insulation
|
||||
- Pro Clima Intello: https://www2.proclima.com/products/internal-sealing/intello
|
||||
- Roma NaturBoard FIT Plus: https://roma.rs/naturboard-fit-plus-knauf-4003010
|
||||
- Knauf oficijelni NaturBoard FIT Plus data page: https://knauf.com/sq-XK/p/produkt/naturboard-fit-plus-23858_4217
|
||||
- Roma izolacija krova i parne brane: https://roma.rs/izolacija-krova
|
||||
- Crafter letva 3x5 3m: https://www.crafter.rs/letva-3x5-3m-090100302
|
||||
- Krajina drvo lamperija: https://krajinadrvo.rs/proizvodi/brodski-pod-i-lamperija/lamperija/
|
||||
- Aquaplan lamperija od finske omorike: https://www.aquaplanshop.rs/obloge-za-saune/lamperija-od-finske-omorike-14x95mm.html
|
||||
- Daibau - pregled cena i smernice: https://www.daibau.rs/clanak/1857/izolacija_potkrovlja_-_stiropor_ili_mineralna_vuna
|
||||
- Daibau - cenovni reperi po m2: https://www.daibau.rs/imenik/izolacija_potkrovja/trgoviste
|
||||
- Svezaskele izolacija potkrovlja: https://www.svezaskele.rs/izolacija-potkrovlja/
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,20 @@
|
||||
# Potkrovlje - pregled fajlova
|
||||
|
||||
Otvori ovim redom:
|
||||
|
||||
1. `contact-sheet.png`
|
||||
2. `potkrovlje_izvestaj.md`
|
||||
3. `zoning-plan.png`
|
||||
4. `render-1-warm-nordic.png`
|
||||
5. `render-2-soft-japandi.png`
|
||||
6. `render-3-rustic-studio.png`
|
||||
|
||||
Brzi orijentir:
|
||||
- `Warm Nordic Loft` je moja glavna preporuka
|
||||
- `Soft Japandi Suite` je mirnija i svetlija varijanta
|
||||
- `Rustic Studio Loft` je tamnija i dramaticnija varijanta
|
||||
|
||||
Kad pregledas, sledeci korak je da izaberes:
|
||||
- jedan stil
|
||||
- jednu funkciju prostora: vise spavanje, vise lounge, ili mesovito
|
||||
- da li hoces 15 cm ili 20 cm izolacije
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,162 @@
|
||||
# Potkrovlje - izolacija krova i koncept uredjenja
|
||||
|
||||
Datum provere cena: 2026-03-25
|
||||
|
||||
## 1. Sta sam uzeo kao bazu
|
||||
- Bruto povrsina iz dostavljene tabele: `79.29 m2`
|
||||
- Povrsina terase iz tabele: `5.89 m2`
|
||||
- Za krov sam radio dve varijante:
|
||||
- ako terasa nije pod krovom: `76.92 m2` kosina
|
||||
- ako puna bruto projekcija ulazi u krov: `83.09 m2` kosina
|
||||
- Za nabavku sam kao realan radni scenario uzeo:
|
||||
- `84.61 m2` sa 10% rezerve u varijanti bez terase pod krovom
|
||||
- gornji bezbedni scenario: `91.40 m2` sa 10% rezerve
|
||||
|
||||
## 2. Moja tehnicka preporuka
|
||||
|
||||
### Preporuceni sistem
|
||||
Od spolja ka unutra:
|
||||
1. postojeci crep
|
||||
2. postojeca paropropusna folija
|
||||
3. postojece daske
|
||||
4. proverena ventilisana distanca 3-5 cm ako membrana nije sigurno potvrdena kao ispravna i dovoljno difuzno otvorena
|
||||
5. 10 cm kamena vuna izmedju rogova
|
||||
6. dodatnih 5 cm ili 10 cm ispod rogova u kontinuitetu
|
||||
7. unutrasnja aktivna parna brana / pametna membrana ili bar kvalitetna parna brana sa zadihtovanim spojnicama
|
||||
8. podkonstrukcija za instalacioni sloj
|
||||
9. drvena obloga - lamperija ili brodski pod, ne gips
|
||||
|
||||
### Sta je najpametnije za tvoj slucaj
|
||||
- Ako hoces balans cena / trajnost / zvuk / protivpozarno: `20 cm ukupno` mineralne vune i drvena obloga.
|
||||
- Ako hoces da ustedis, ali da i dalje bude korektno: `15 cm ukupno`.
|
||||
- Ako membrana ispod crepa nije proverena ili je starija i nepoznata, ja ne bih nabijao vunu direktno u daske bez kontrole detalja.
|
||||
|
||||
### Ocekivani termicki nivo
|
||||
- `15 cm` mineralne vune: okvirno `U oko 0.23-0.26 W/m2K`
|
||||
- `20 cm` mineralne vune: okvirno `U oko 0.18-0.21 W/m2K`
|
||||
|
||||
Zakljucak: za potkrovlje koje zelis da koristis kao ozbiljan stambeni prostor, ja bih isao na `20 cm`.
|
||||
|
||||
## 3. Kvadratura i kolicine
|
||||
|
||||
### Radna nabavna kvadratura
|
||||
- Preporuka za obracun i porucivanje: `85 m2`
|
||||
- Ako zelis potpuno bezbednu gornju rezervu: `91-92 m2`
|
||||
|
||||
### Zapremina izolacije
|
||||
- Za `15 cm`: oko `12.69 m3` izolacije sa rezervom
|
||||
- Za `20 cm`: oko `16.92 m3` izolacije sa rezervom
|
||||
|
||||
## 4. Cene sa neta i kako sam ih citao
|
||||
|
||||
### Izolacija i membrane
|
||||
- `NaturBoard FIT Plus Knauf` na Roma: `od 436 RSD/kom`, proizvod za kosi krov, kamena vuna. Na Knauf oficijelnom listu za varijantu 100 mm stoji ploca `600 x 1000 mm`, 6 kom u paketu = `3.60 m2/pak`. Iz toga izlazi maloprodajni reper od oko `727 RSD/m2` za `10 cm`.
|
||||
- `KNAUF PARNA BRANA LDS 5 Silk 75m2` na Roma: `10,030 RSD/rolna`
|
||||
- `KNAUF PARNA BRANA LDS 200 AluPlus 75m2` na Roma: `8,685 RSD/rolna`
|
||||
- `Knauf HomeSeal LDS Solifit 1 25m` na Roma: `2,050 RSD/rolna`
|
||||
|
||||
### Drvena podkonstrukcija i obloga
|
||||
- `Letva 3x5 3m` na Crafter: `259 RSD/kom`
|
||||
- `Lamperija severna jela` na Krajina drvo / KP: `1,370 RSD/m2`
|
||||
- `Camova lamperija 10 mm` na Krajina drvo: `1,000 RSD/m2`
|
||||
- Premium sauna omorika na Aquaplan: `460 RSD/m duzni`, a za `1 m2` treba `10.52 m duznih`, sto izadje oko `4,839 RSD/m2`
|
||||
|
||||
### Ruke - reperi sa trzista
|
||||
- Daibau za izolaciju potkrovlja daje vise stavki po m2; za `NaturBoard FIT PLUS` prikaz je `5.30-7.90 EUR/m2`, a za `Homeseal LDS 0.02` oko `0.80 EUR/m2`
|
||||
- Svezaskele navodi prosecnu cenu izolacije potkrovlja `7-12 EUR/m2 plus materijal`
|
||||
|
||||
### Sta iz ovoga zakljucujem
|
||||
- Cene materijala za Srbiju koje sam nasao online podrzavaju realan raspon:
|
||||
- izolacija `15 cm`: oko `1,100-1,300 RSD/m2`
|
||||
- izolacija `20 cm`: oko `1,450-1,700 RSD/m2`
|
||||
- kvalitetna unutrasnja membrana: oko `135-200 RSD/m2` po rolnama, ali sa realnim nabavnim otpadom racunaj vise
|
||||
- drvena obloga: realno `1,000-1,370 RSD/m2` za normalnu enterijersku lamperiju
|
||||
|
||||
## 5. Troskovnik koji bih ja koristio za odluku
|
||||
|
||||
Napomena: ovo je `procena`, ne ponuda izvodjaca. U total nisam stavljao teske opravke krova, zamenu trulih dasaka, novu foliju spolja, skelu spolja ili dekorativnu rasvetu.
|
||||
|
||||
### Varijanta A - korektna i racionalna, 15 cm
|
||||
- Obracunska povrsina: `84.61 m2`
|
||||
- Materijal okvirno: `288,000-311,000 RSD`
|
||||
- Ruke okvirno: `129,000-179,000 RSD`
|
||||
- Ukupno okvirno: `418,000-490,000 RSD`
|
||||
|
||||
Sta dobijas:
|
||||
- 10 cm izmedju rogova + 5 cm dodatni sloj
|
||||
- parna brana / aktivna membrana
|
||||
- podkonstrukcija
|
||||
- drvena lamperija kao final
|
||||
|
||||
### Varijanta B - moja preporuka, 20 cm
|
||||
- Obracunska povrsina: `84.61 m2`
|
||||
- Materijal okvirno: `354,000-378,000 RSD`
|
||||
- Ruke okvirno: `148,000-208,000 RSD`
|
||||
- Ukupno okvirno: `502,000-586,000 RSD`
|
||||
|
||||
Sta dobijas:
|
||||
- 20 cm ukupne izolacije
|
||||
- bolji zimski komfor i bolju zastitu leti
|
||||
- manji rizik da kasnije pozelis jos jedan dodatni sloj
|
||||
|
||||
### Gornji scenario ako se na licu mesta ispostavi da treba racunati punih 91-92 m2
|
||||
- 15 cm: oko `447,000-524,000 RSD`
|
||||
- 20 cm: oko `535,000-625,000 RSD`
|
||||
|
||||
### Moj savet za budzet
|
||||
- Ako zelis da uradis jednom i mirno: planiraj `550,000-620,000 RSD` za preporucenu varijantu 20 cm.
|
||||
- Ako zelis da ne preteras, ali da bude dobro: planiraj `440,000-500,000 RSD` za 15 cm.
|
||||
- Obe varijante dodatno zastiti sa jos `5-10%` rezerve za nepredvidjene detalje oko terasnih vrata, prodora, stepenisnog otvora i eventualnih krivih rogova.
|
||||
|
||||
## 6. Dizajn preporuka za samo potkrovlje
|
||||
|
||||
## Glavna ideja koju preporucujem
|
||||
`Warm Nordic Loft` je najbolja osnova za tvoj prostor jer vec imas:
|
||||
- lepu opeku na zabatima
|
||||
- vidljivu drvenu krovnu konstrukciju kao karakter
|
||||
- dobru prirodnu svetlost i pogled
|
||||
|
||||
Zato ne bih pravio "teski" rustik niti hladan minimalizam. Isao bih na:
|
||||
- svetlu drvenu oblogu plafona u mat ulju
|
||||
- ociscenu i prelakiranu opeku ili blago fugovanje, ne potpuno zatvaranje
|
||||
- pod u srednje toplom hrastu ili kvalitetnom SPC/engineered oak tonu
|
||||
- niske plakare pod obe kosine, dubina `45-60 cm`
|
||||
- crna ili tamno bronzana rasveta po slemenu i na zabatima
|
||||
- tekstil u toplim zemljanim tonovima: pesak, maslina, glina, siva lanena
|
||||
|
||||
## Predlog rasporeda
|
||||
- Zona uz terasu i pogled: lounge + citaonica + mali radni sto
|
||||
- Mirniji kraj uz dva prozora: spavaca zona
|
||||
- Pod kosinama sa obe strane: neprekinuti niski plakari
|
||||
- Oko stepenisnog otvora: lagana ograda od drveta i metala, ne puna zidna pregrada
|
||||
|
||||
## 7. Lokalni koncept renderi i skice
|
||||
|
||||
Napravio sam lokalne koncept-vizuale u folderu `renders/`. To nisu fotorealisticki renderi iz SketchUp/3ds Max merenog modela, nego namerno ciste koncept ilustracije bazirane na tvojoj osnovi i fotografijama, da mozemo brzo da zakljucamo pravac.
|
||||
|
||||
Fajlovi:
|
||||
- `renders/contact-sheet.png`
|
||||
- `renders/zoning-plan.png`
|
||||
- `renders/render-1-warm-nordic.png`
|
||||
- `renders/render-2-soft-japandi.png`
|
||||
- `renders/render-3-rustic-studio.png`
|
||||
|
||||
## 8. Sta bih ja uradio prvo na terenu
|
||||
1. proverio stanje folije i dasaka ispod crepa
|
||||
2. izmerio stvarnu dubinu rogova i raster
|
||||
3. proverio gde tacno moze da ide ventilacioni kanal
|
||||
4. odlucio da li zadrzavas potpuno otvoren loft ili hoces pregradu za spavaci deo
|
||||
5. tek onda zatvorio finalnu debljinu izolacije i tacnu nabavnu listu
|
||||
|
||||
## 9. Izvori
|
||||
- Knauf roof insulation: https://www.knaufinsulation.com/what-we-do/our-solutions/mineral-wool-insulation-for-buildings/roof-insulation
|
||||
- Pro Clima Intello: https://www2.proclima.com/products/internal-sealing/intello
|
||||
- Roma NaturBoard FIT Plus: https://roma.rs/naturboard-fit-plus-knauf-4003010
|
||||
- Knauf oficijelni NaturBoard FIT Plus data page: https://knauf.com/sq-XK/p/produkt/naturboard-fit-plus-23858_4217
|
||||
- Roma izolacija krova i parne brane: https://roma.rs/izolacija-krova
|
||||
- Crafter letva 3x5 3m: https://www.crafter.rs/letva-3x5-3m-090100302
|
||||
- Krajina drvo lamperija: https://krajinadrvo.rs/proizvodi/brodski-pod-i-lamperija/lamperija/
|
||||
- Aquaplan lamperija od finske omorike: https://www.aquaplanshop.rs/obloge-za-saune/lamperija-od-finske-omorike-14x95mm.html
|
||||
- Daibau - pregled cena i smernice: https://www.daibau.rs/clanak/1857/izolacija_potkrovlja_-_stiropor_ili_mineralna_vuna
|
||||
- Daibau - cenovni reperi po m2: https://www.daibau.rs/imenik/izolacija_potkrovja/trgoviste
|
||||
- Svezaskele izolacija potkrovlja: https://www.svezaskele.rs/izolacija-potkrovlja/
|
||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,19 @@
|
||||
# Warm Nordic - pregled
|
||||
|
||||
Otvori ovim redom:
|
||||
|
||||
1. `warm-nordic-contact-sheet.png`
|
||||
2. `warm-nordic-layout-plan.png`
|
||||
3. `warm-nordic-lounge-render.png`
|
||||
4. `warm-nordic-studio-render.png`
|
||||
5. `warm-nordic-koncept.md`
|
||||
|
||||
Sta gledas:
|
||||
- lounge strana prema terasi sa TV-om i coffee nook
|
||||
- studio strana prema stepenicama sa recording desk-om i listening lounge
|
||||
- centralni storage volumen oko stubova i odzaka kao dizajn feature
|
||||
|
||||
Ako zelis sledeci krug dorade, biramo:
|
||||
- vise svetla / vise tamnog kontrasta
|
||||
- vise "soft nordic" ili vise "clean luxury"
|
||||
- koliko otvoren ili zatvoren da bude storage core
|
||||
|
After Width: | Height: | Size: 5.9 MiB |
@@ -0,0 +1,28 @@
|
||||
# Warm Nordic Lounge + Studio
|
||||
|
||||
## Finalni pravac
|
||||
- terasa strana: living room / lounge
|
||||
- TV je vezan za centralni storage volumen
|
||||
- coffee making kutak je uz lounge zonu, blizu terase
|
||||
- stepenice strana: home music recording studio + listening lounge
|
||||
- izmedju: storage spine oko stubova i odzaka
|
||||
|
||||
## Kako storage poboljsava ambijent
|
||||
- obloziti centralni volumen hrastovim vertikalnim letvicama ili finim furnirom
|
||||
- gornje fronte zatvorene i mirne, push-open, bez vizuelnog nereda
|
||||
- po jedna otvorena nisa prema svakoj zoni za dekor, knjige, ploce ili kafe set
|
||||
- TV strana ostaje cista i smirena
|
||||
- studio strana dobija integrisanu akusticku logiku i skriveni kablovski management
|
||||
|
||||
## Materijali
|
||||
- svetliji hrast / smoked oak u toplom tonu
|
||||
- opeka ostaje vidljiva i ociscena
|
||||
- plafonska drvena obloga prirodan mat ton
|
||||
- tekstil: lan, greige, pesak, maslina
|
||||
- metalni detalji: crni ili tamno bronzani
|
||||
|
||||
## Napomena
|
||||
Ovi vizuali su high-res textured concept renders, ne pravi V-Ray / Corona / AI photo renders.
|
||||
Za potpuno fotorealistican rezultat na tacnoj geometriji treba ili:
|
||||
1. originalne fotografije kao lokalni fajlovi za photomontage, ili
|
||||
2. 3D model u SketchUp / Blender + render engine.
|
||||
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 45 KiB |