Grafikák készítése objektumokba foglalt, sokszögből előállított síkidomokkal

A korábbi bejegyzésekben többször foglalkoztunk azzal a kérdéskörrel, hogy a tkinter vászon (Canvas) grafikus elemén hogyan lehet egy pontot forgatni és tükrözni. Ezek elvi alapjait példákkal a „Hogyan forgassunk el egy adott pontot adott szöggel, adott forgáspont körül?” és „Hogyan valósítsuk meg egy pont tengelyes tükrözését?” korábbi cikkek tartalmazzák. Mindezt azért is tettük, mert a vásznon alapban megjeleníthető rajzelemek többsége (ellipszis, ellipszisív és téglalap) nem forgatható el alaktartóan, vagyis a forgatás után torzulnak, és az ellipszis tengelyei, valamint a téglalap oldalai továbbra is a vízszintes és függőleges koordináta-tengelyekkel lesznek párhuzamosak.

Ha alaktartó forgatást szeretnénk, akkor azt csak úgy érhetjük el, hogy az említett síkidomokat egy-egy sokszöggel valósítjuk meg, mert ha egy sokszög minden csúcspontjára alkalmazzuk a forgatást, akkor maga a sokszög alaktartóan fog elfordulni az adott forgáspont körül. Ezzel foglalkoztunk a „Sokszög forgatása és tükrözése vizuálisan grafikus alkalmazással” című bejegyzésben. Ezen ismeretek birtokában pedig megnyílik az út ellipszis, ellipszisív és téglalap forgatására is. Ennek egy lehetséges megoldásával foglalkozott a „Forgatható ellipszis, ellipszisív és téglalap” című bejegyzés. Itt a Canvas osztályt specializáltuk egy olyan alosztállyal, amely képes a címében szereplő olyan síkidomokat előállítani, amelyeket alaktartóan lehet forgatni egy pont körül. Ha nem csak e három síkidomot kívánjuk forgathatóvá tenni, hanem másokat is, akkor minden ilyenhez egy új létrehozó metódust kell definiálni a specializált Canvas alosztályban. Ez azonban nem igazán jó megközelítés, mert vagy az alosztályt kell módosítani minden új síkidomhoz, ami a nyitott-zárt elvet (open-closed principle) sérti, vagy ezt elkerülendő mindig egy új alosztályt kell létrehozni, aminek szintén vannak hátrányai (pl. áttekinthetőség és karbantarthatóság csökkenése; az öröklési hierarchia változtatással szembeni sérülékenysége; a kliens kód esetleges módosítási szükségessége.)

Mindezek miatt egy másik megközelítést mutatunk e bejegyzésben.

Az elv az, hogy számbavesszük, hogy egy vászon grafikus elemen megjelenített sokszöggel megvalósított síkidomokhoz milyen számunkra fontos, általános, vagyis minden síkidomra értelmezhető műveletek rendelhetők. Ezeket egy közös alaposztályba szervezzük, aminek neve legyen PolygonGraphics. Ezen osztály metódusaiként definiáljuk tehát például

  • a geometriai transzformációkat (pl. forgatás, tükrözés, eltolás, áthelyezés, átméretezés),
  • a grafika befoglaló téglalapja ellentétes sarokpontjainak meghatározását, és e téglalap középpontjának meghatározását,
  • a sokszög csúcspontjainak középpontjának (súlypontjának) meghatározását,
  • a sokszög oldalhosszainak kikérését,
  • a sokszög csúcspontjainak kikérését és megváltoztatását,
  • a sokszög egy adott, vagy minden konfigurációs paramétere értékének kinyerését,
  • a sokszög konfigurációs jellemzőinek (pl. kitöltőszín, körvonalszín, körvonal vastagság) beállíthatóságát,
  • címkék (tag-ek) hozzárendelését, törlését, lekérdezését,
  • események és eseménykezelők hozzárendelését és törlését

A felsorolt metódusok működéséhez kell néhány privát segédmetódus, mint például a korábbi „Sokszögek csúcspontjainak sorbarendezése a helyes megjelenítéshez” bejegyzésben ismertetettek. De ilyen lehet egy ellipszis pontjait adott szögtartományban szolgáltató metódus is, ami egyéni síkidomok megalkotásakor könnyítheti a munkát.

Az egyes, sokszöggel megvalósított síkidomokat szintén egy-egy osztályban definiáljuk, amelyek öröklik a PolygonGraphics osztályt, vagyis a konkrét síkidomosztályok a PolygonGraphics alosztályai lesznek. Ha így teszünk, akkor az egyes síkidomosztályok szerkezete egyszerű lesz, mert kötelezően csak egy olyan metódust kell tartalmazni, ami a  síkidom grafikát létrehozza sokszögből a Canvas osztály create_polygon() metódusának hívásával vagy egy már létező sokszög felhasználásával. A kötelezően implementálandó metódusnak azonos neve lehet minden alosztályban, ami legyen most _create_graphics(). Mivel a grafika létrehozás és kirajzolás minden síkidom esetén egy kötelezően megvalósítandó feladat, ezért a _create_graphics() meghívását betehetjük a PolygonGraphics szülőosztályba, konkrétan annak __init__ metódusába. Ezzel párhuzamosan a PolygonGraphics osztályban definiálunk egy absztrakt _create_graphics() metódust, ami kikényszeríti az alosztályokban történő implementálást. Ezzel a PolygonGraphics osztály absztrakt osztály lesz, ezért az abc modul ABC osztályát örökölnie kell.

A grafika létrehozás és kirajzolás természetesen egy adott Canvas példányon történik, ezért mind a PolygonGraphics mind alosztályai konstruktora kell, hogy egy Canvas példányt fogadjon. És ugyanezen okból mind a PolygonGraphics mind alosztályai konstruktorában lehetőséget kell biztosítani kulcsszavas argumentumokkal, hogy a sokszög konfigurációját meg lehessen adni.

Ezeken felül a konkrét síkidomokat megvalósító alosztályok konstruktorában kell megadni a sokszög csúcspontjait is olyan síkidomoknál, ahol a síkidom maga ténylegesen sokszög. Ott, ahol ez releváns lehet, az ilyen síkidom geometriai meghatározására alternatív módot is lehet biztosítani egy-egy osztálymetódussal. Így például a téglalap és négyzet esetén az osztálymetódus a csúcspont-koordináták helyett az oldalhosszakat és a kezdeti pozíció koordinátáit fogadhatja. Bármilyen geometriai adatokkal is határozzuk is meg a síkidomokat a példányosító osztálymetódusokban, követelmény, hogy a Canvas példányt és a sokszög konfigurációját is meg lehessen adni.

Az olyan síkidomok esetén, amelyek nem sokszögek, hanem csak sokszögeket használunk a közelítésükre (pl. ellipszis vagy kör) az implementáló osztály konstruktora természetesen a csúcspontok helyett más adatot fog fogadni. Például kör esetén ezek lehetnek a sugárhossz és a középpont.

Négyszögek esetén a konstruktorokban felhasználhatjuk az előző, „Négyszögfajták ellenőrzése” című bejegyzésben bemutatott ellenőrzőfüggvényeket.

Hasznos művelet még egy síkidom másolása, klónozása. Mivel ez minden síkidom esetén elvégezhető, ezért a PolygonGraphics szülőosztályban definiáljuk clone() néven. Az egyetlen gond, hogy e metódus által visszaadott új példányt az adott konkrét síkidomtól függően más-más módon kell előállítani. Ezt a problémát úgy oldjuk fel, hogy egy _instance_factory() nevű közös absztrakt példánylétrehozó metódust definiálunk a PolygonGraphics osztályban, amit a clone() meghív. Mivel az _instance_factory() absztrakt, ezért azt kötelező minden konkrét síkidomosztálynak implementálnia a sajátosságainak megfelelően. Ez azonban nem bonyolult, többnyire egy egyszerű konstruktorhívás lesz.

Az eddig ismertetett metódusokon felül igény szerint mások is definiálhatók, legyenek azok általánosak, mint például a terület és kerület, vagy síkidomspecifikusak mint például háromszög esetén annak nevezetes pontjait meghatározók.

A grafikák előállításának lehetőségeit nagymértékben bővíti, ha az egyes síkidomokat csoportba tudjuk foglalni, és a különféle műveleteket (pl. forgatást, tükrözést, áthelyezést) a csoport egészére tudjuk végezni. Ezt egy Group nevű osztállyal valósítjuk meg. Mivel ez nem grafikákat hoz létre, hanem meglévő grafikus objektumokat tárol és ezen végez műveleteket, ezért ennek konstruktora csak a csoportba foglalni kívánt PolygonGraphics típusú objektumokat kell, hogy fogadja, a Canvas példányt és a konfigurációs értékeket nem. Ami a metódusokat illeti, a tartalmazásvizsgálaton, iterálhatóságon és igazságérték meghatározásán, valamint a csoporthoz adás és abból való eltávolítás műveletén felül a PolygonGraphics metódusai közül azokat valósítjuk meg, amelyek csoportra értelmezhetők.

A fenti elvek alapján elkészített PolygonGraphics osztály definícióját láthatjuk alább:

A Group osztály definíciója pedig így néz ki:

Mivel ezek az alapvető osztályok a sokszögekkel megvalósított síkidomokkal készítendő grafikákákhoz, ezért ezek egy közös, fundamental_classes nevű modulba szerepelnek.

A négyszögek alosztályai (deltoid, trapéz, paralelogramma, rombusz, téglalap, négyzet) a quadrilaterals modulba vannak foglalva:

Az ellipszis és kör osztályai:

A háromszög osztálydefiníciója:

És végül egy egyéni tervezésű alakzat osztály definíciója:

A részletes kommentek segítik az osztálydefiníciók megértését. A működéshez Python 12+ verzió szükséges.

Alkalmazási példák

Két alkalmazási példát mutatunk ezen osztályok használatára. Ezek kódja az alábbi

Alkalmazási példa 1: Két, keresztező kard.

Futtatás után megjelenő kép:

Alkalmazási példa 2: Absztrakt alkotás, ami felülnézetben lehet akár egy park a közepén szökőkúttal, vagy akár egy futópálya.

Futtatás utáni kép:

A forráskódok letölthetők a https://github.com/pythontudasepites/polygon_graphics linkről.

A bemutatott programkódok megértéséhez, megírásához vagy továbbfejlesztéséhez a tkinter modul használatával történő grafikus felhasználói felület készítésének ismerete nélkülözhetetlen. Ezzel, illetve ezen belül a Canvas rajzelemekkel, példákkal illusztrálva a Python tudásépítés lépésről lépésre című e-könyv „Grafikus felhasználói felület készítése” fejezete foglalkozik. E mellett természetesen számos más ismeret is szükséges, amit az e-könyv szintén tartalmaz, például e bejegyzésben is megjelenő absztrakt osztályok, amelyekkel a könyv „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezeten belül az „Absztrakt osztályok” alfejezet tárgyal részletesen.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.