Ohjelmointi

ICATC2070 Software Engineering Project, Environmental Conditions Monitoring

Seuraava video ja kaaviokuva ovat tekniikan kandidaatin opintoihini kuuluneesta IT-projektista, syksyltä 2018. Sen tarkoituksena oli suunnitella sensorien valvontajärjestelmä erilaisten tehdaslaitteiden tilan seurantaan.

Oma tehtäväni projektissa oli sensoritietokannan scheman suunnittelu, web UI:n osittainen ohjelmointi sekä MQTT/tietokanta/web UI palvelimen ylläpito. Projekti-tiimiin kuului minun lisäkseni kaksi muuta opiskelijaa Jani Lehtisalo (projektin johtaminen, dokumentointi ja web UI:n back endin osittainen ohjelmointi) ja Miguel Sluijs (arduino ohjelmointi, web UI:n front endin osittainen ohjelmointi).

Järjestelmä koostui arduinoon kytketyistä sensoreista, jotka lähettivät sensoridatansa MQTT palvelimelle, josta ne luettiin tietokantaan. Tietokanssa olevaa dataa pystyttiin sen jälkeen selaamaan web UI:n kautta.

Esimerkkivideo järjestelmän toiminnasta, video projektin muiden jäsenten käsialaa.

Kaaviokuva projektin arkkitehtuurista, tehty yEd Graph Editor ohjelmalla.

FPGA:lle ohjelmoitu 8-Bittinen tietokone

Seuraavat kuvat ovat otteita kirjoittamastani raportista, joka käsittelee loppukevään 2018 aikana FPGA:lle toteuttamaani 8-bittistä tietokonetta. Kyseinen raportti löytyy tämän linkin alta. Tietokone perustuu Albert Paul Malvinon ja Jerald A. Brownin 'Digital Computer Electronics' kirjan SAP-1 (Simple-As-Possible) tietokoneeseen ja Ben Eaterin saman kirjan samaa tietokonetta käsittelevään YouTube sarjaan. Minulla on myös työn alla versio saman kirjan ominaisuuksiltaan laajemmasta SAP-2 koneesta, joka toteutettu parametrisesti ohjelmoiduilla System Verilog koodilla, graafisten .bdf kaavioiden sijaan.

Tietokoneen 16-tavuisen muistipiirin .bdf piirikaavio.

Tietokoneen simulodun testiajon signaalikaavio.

Ote tietokoneen fyysisellä FPGA piirillä ajetusta manuaalisesti askelletusta testiajosta. Kuvasarja esittää tietokoneen signaaleissa SUB komennon ajon aikana tapahtuvia muutoksia.

Geneerisiä PHP Funktioita

Näiden PHP funktioiden tarkoitus on auttaa dynaamisten ja turvallisten pdo MySQL hakukyselyjen kokoamisessa. Ne toteutettiin osana projektia, jonka tein kandidaatintutkielmanani.

Kyseisen kandidaatintutkielman raportti löytyy tämän linkin alta.

Funktiot generoivat pdo alikyselymerkkijonoja, sen perusteella, mitkä kyselyfunktioon sisään tulevista arvoista sisältävät tietoa ja mitkä ovat tyhjiä.

Tuloksena olevat alikyselyjonot voidaan ketjuttaa yhdeksi MySQL kyselyksi ja sisältävät valmiit pdo paikkamerkit, joihin käyttäjän syötteet voidaan sitoa.


<?php

/** 
* Query generation functions
*/

/**
* Generates a generic subquery
*
* If the input data $query_input is not empty, checks $query_field and
* $query_placeholder against the whitelists $query_allowed_fields and
* $query_allowed_placeholders then returns a string in format:
* "`query_field` = :query_placeholder "
* otherwise returns the empty string "".
*
* The whitelists $query_allowed_fields and $query_allowed_placeholders
* should have hardcoded values to maintain security.

* @param    $query_input                The value that would replace the
*                                       placeholder and be inserted into
*                                       the query when it is run.

* @param    $query_field                The field to be queried.

* @param    $query_placeholder          The placeholder to associate
*                                       with the field.

* @param    $query_allowed_fields       The whitelist of allowed query
*                                       field names.

* @param    $query_allowed_placeholders The whitelist of allowed
*                                       placeholder names.

* @return   A pdo query string in the format:
*           "`query_field` = :query_placeholder "
*           or an empty string.
*/
function generic_subquery($query_input$query_field$query_placeholder,
                      
$query_allowed_fields,
                      
$query_allowed_placeholders)

$query="";
if(!
custom_empty($query_input))
{
backtick($query_field,"");
$query_field_key array_search($query_field,$query_allowed_fields);
$query_field $query_allowed_fields[$query_field_key];

$query_placeholder_key array_search($query_placeholder,
                                      
$query_allowed_placeholders);
$query_placeholder =
$query_allowed_placeholders[$query_placeholder_key];

$query=$query_field." = :".$query_placeholder;
}
return 
$query;
}

/**
* Generates a generic query for any of a given array of values.
*
* Calls the generic_subquery() function to query $query_field for every
* value of the $query_input_array and concatenates them together with
* the concatenate_or() function.

* @param    $query_input_array          An array of values to query 
*                                       $query_field for.

* @param    $query_field                The field to be queried.

* @param    $query_placeholder          The placeholder to associate
*                                       with the field.

* @param    $query_allowed_fields       The whitelist of allowed query
*                                       field names.

* @param    $query_allowed_placeholders The whitelist of allowed
*                                       placeholder names.

* @return   A string containing a series of return values from the
*           generic_subquery() function, concatenated together with the
*           concatenate_or() function.
*/
function generic_subquery_from_array($query_input_array$query_field,
                                
$query_placeholder_array,
                                
$query_allowed_fields,
                                
$query_allowed_placeholders)
{
$query="";
if(
count($query_input_array)==count($query_placeholder_array))
{
    
$query=generic_subquery($query_input_array[0], $query_field,
                            
$query_placeholder_array[0],
                            
$query_allowed_fields,
                            
$query_allowed_placeholders);
    for(
$i 1$i count($query_input_array); $i++)
    {
        
$query=$query.concatenate_or(
                    
generic_subquery($query_input_array[$i],
                                    
$query_field,
                                    
$query_placeholder_array[$i],
                                    
$query_allowed_fields,
                                    
$query_allowed_placeholders
                                    
)
                                    );
    }
    
$query=concatenate_brackets($query);
}
return 
$query;
}

/**
* Generates an ID subquery.
*
* Returns a string in format:
* " `id_field` = :id_field "
* or if $id_input is custom_empty()
* " `id_field` IS NOT NULL "
*
* $id_input is the input data, it's only checked for whether it's
* custom_empty() or not.

* custom_empty() is a modified empty() function
* that considers the number zero, whether in integer, decimal or
* string format, to be a valid number instead of null.
*
* $id_field should be hardcoded string with no user access to maintain
* security.

* @param    $id_input   The id value to search for.
* @param    $id_field   The name of the id field to search for the
*                       $id_input, should be hardcoded string with no
*                       user access for security.

* @return   A pdo query string in the format:
*           " `id_field` = :id_field "
*           or if $id_input is custom_empty()
*           " `id_field` IS NOT NULL "
*/
function id_subquery($id_input$id_field)
{      

$id_query " ".backtick($id_field,"")." = :".$id_field;

if(
custom_empty($id_input))
{
$id_query " ".backtick($id_field,"")." IS NOT NULL ";}

return 
$id_query;
}

/**
* Generates a range subquery.
*
* Returns the string:
* " AND `range_field` >= :range_field_start "
* or
* " AND `range_field` <= :range_field_end "
* or 
* " AND `range_field` >= :range_field_start AND
*  `range_field` <= :range_field_end "
* or
* the empty string "" depending on which input data variables exist.
*
* $range_start & $range_end are the user provided values, they're only
* checked for whether they're empty or not.
*
* $range_field should be hardcoded string with no user access to
* maintain security.

* @param    $range_start    The starting value of the range to query.
* @param    $range_end      The end value of the range to query.
* @param    $range_field    The field to search for values in th range.

* @return   A pdo query string in the format:
*           " AND `range_field` >= :range_field_start "
*           or
*           " AND `range_field` <= :range_field_end "
*           or 
*           " AND `range_field` >= :range_field_start AND
*            `range_field` <= :range_field_end "
*           or
*           the empty string "".
*/
function range_subquery($range_start$range_end$range_field)
{
$range_return "";

$start_query " AND ".backtick($range_field,"").
             
" >= :".$range_field."_start ";
             
$end_query " AND ".backtick($range_field,"").
           
" <= :".$range_field."_end "

if(!
custom_empty($range_start))
{
$range_return $range_return.$start_query;}

if(!
custom_empty($range_end))
{
$range_return $range_return.$end_query;}

return 
$range_return;
}

/**
* Generates a sorting subquery.
*
* Checks $by_field against the whitelist $allowed_fields and compares
* $order_in to "ASC" to determine what $order_out should be, then
* returns a string in the format:
* " ORDER BY 'by_field' order_out" or
* " ORDER BY 'default_field' order_out" if $by_field was custom_empty.
*
* The whitelist $allowed_fields and variable $default_field
* should have hardcoded values to maintain security.

* @param    $by_field       The name of the field to sort by.

* @param    $default_field  The field to default to, if $by_field is
*                           empty or not in the $allowed_fields
*                           whitelist.

* @param    $order_in       What order to sort in, defaults to ASC.

* @param    $allowed_fields The whitelist of allowed query
*                           field names.

* @return   A pdo query string the format:
*           " ORDER BY 'by_field' order_out" or
*           " ORDER BY 'default_field' order_out"
*           if $by_field was custom_empty.
*/
function sort_subquery($by_field$default_field$order_in,
                   
$allowed_fields)
{

$by_field backtick($by_field$default_field);

$key=array_search($by_field,$allowed_fields);
$by_field=$allowed_fields[$key];

$order_out "ASC";
if(
$order_in == "DESC"){$order_out="DESC";}

return 
" ORDER BY $by_field $order_out";
}
?>

Seuraavat funktiot ovat samasta kandidaatintutkielmaprojektista peräisin olevia merkkijonojen manipulointiin ja syötteiden tarkastamiseen tarkoitettuja apufunktioita.


<?php

/**
* String manipulation functions.
* /

/**
* Backticking function.
*
* Returns backticked $bt_input,
* or backticked $default_value if $bt_input was custom_empty,
* or an empty string if $default_value was custom_empty
*
* Replaces double backticks with singles if $_bt_input
* or $default_value were already backticked

* @param    $input_to_tick  The input to tick.
* @param    $default_value  The value to default to if $input_to_tick
*                           doesn't exist.

* @return   A backticked $input_to_tick or $default value.
*/
function backtick($input_to_tick$default_value) {

if(!
custom_empty($input_to_tick))
{
$input_to_tick "`".str_replace("`","``",$input_to_tick)."`";}

else if(!
custom_empty($default_value))
{
$input_to_tick "`".str_replace("`","``",$default_value)."`";}
else {
$input_to_tick "";}

return 
$input_to_tick;
}

/**
* Puts the input string in brackets if it exists.
*
* Use only with static strings or validated input for security.

* @param    $bracket_input  The input to put brackets around.
* @return   The bracketed $bracket input.
*/
function concatenate_brackets($bracket_input) {
if(!
custom_empty($bracket_input)){
return 
" (".$bracket_input.") ";
}
else { return 
""; }
}

/**
* Adds " AND " to the beginning of the input string if it exists.
*
* Use only with static strings or validated input for security.

* @param    $and_input  The input to add AND to.
* @return   " AND ".$and_input
*/
function concatenate_and($and_input) {
if(!
custom_empty($and_input)){
return 
" AND ".$and_input;
}
else { return 
""; }
}

/**
* Adds " OR " to the beginning of the input string if it exists.
*
* Use only with static strings or validated input for security.

* @param    $or_input   The input to add OR to.
* @return   " OR ".$or_input
*/
function concatenate_or($or_input) {
if(!
custom_empty($or_input)){
return 
" OR ".$or_input;
}
else { return 
""; }
}

?>

<?php

/** PDO utility and validation functions */

/**
* The function to prepare pdo.

* @param $host      The host name containing the database.
* @param $db        The database to log PDO into.
* @param $user      The username to log PDO in as.
* @param $pass      The password of $user.
* @param $charset   The character set for PDO to use.
*/ 
function openPDOconnection($host,$db,$user,$pass,$charset)
{

$dsn "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES   => false,
];

$pdo = new PDO($dsn$user$pass$opt);
return 
$pdo;
}

/**
* Binds a value to a pdo query, if it's not custom_empty().
*
* Checks if the $bind_value to be bound exists, if it does, binds it to
* $bind_placeholder in the $bind_query.
*
* @param    $bind_query         A prepared pdo query.
* @param    $bind_placeholder   The pdo placeholder name/number,
*                               ie ':placeholder'.

* @param    $bind_value         The value to bind to the placeholder.
*/
function empty_check_bind($bind_query$bind_placeholder$bind_value)
{
if(!
custom_empty($bind_value))
{
$bind_query->bindValue($bind_placeholder$bind_value);}
}

/**
* Function to check existence of numeric input.
*
* @param    $number_in  The value to check for numeric input.

* @return   $number_in, if it is numeric, else null.
*/
function empty_check_numeric($number_in)
{
if(!
custom_empty($number_in) && is_numeric($number_in)){
return 
$number_in;
}
else{return 
NULL;}
}

/**
* Function to check existence of integer input.
*
* Casts $number_in to int and compares to original $number_in
* if value is maintained the result is acceptable

* @param    $int_in     The value to check for integer input.

* @return   $int_in, if it is integer, else null.
*/
function empty_check_int($int_in)
{
$temp = (int) $int_in;
if(
$temp == $int_in && !custom_empty($int_in)){
return 
$int_in;
}
else{return 
NULL;}
}

/**
* Function to validate string input.
*
* Trims whitespace, strips slashes and html tags

* @param    $validate_input     The input to validate.

* @return   $validate_input, trimmed of whitespace and stripped of
*           slashes and html tags.
*/
function validate($validate_input) {
$validate_input trim($validate_input);
$validate_input stripslashes($validate_input);
$validate_input htmlspecialchars($validate_input);
return 
$validate_input;
}

/**
* String validate function with existence check.
*
* Checks if $ev_input exists, if it does, runs the result through the
* validation function above and returns it.

* @param    $validate_if_not_empty      The input to validate.

* @return   $validate_if_not_empty, trimmed of whitespace and stripped
*           of slashes and html tags. Or just $validate_if_not_empty if
*           it was empty.
*/
function empty_check_validate($validate_if_not_empty)
{
if(!
custom_empty($validate_if_not_empty))
{
$validate_if_not_empty validate($validate_if_not_empty);
}

return 
$validate_if_not_empty;
}

/**
* String validate function with existence check and concatenation.
*
* Checks if $validate_if_not_empty exists, if it does concatenates it
* between $concat_start & $concat_end, runs the result through the
* validation function above and returns it. 

* @param    $concatenate_start          The string to add to the
*                                       beginning of
*                                       $validate if not empty.

* @param    $validate_if_not_empty      The input to validate.

* @param    $concatenate_end            The string to add to the
*                                       end of $validate if not empty.

* @return   $validate_if_not_empty, concatenated between
*           $concatenate_start and $concatenate_end and run through the
*           above empty_check_validate() function. 
*/
function empty_check_validate_concatenate($concatenate_start,
                                      
$validate_if_not_empty,
                                      
$concatenate_end)
{  
if(!
custom_empty($validate_if_not_empty))
{
$validate_if_not_empty validate($concatenate_start.
                                  
$validate_if_not_empty.
                                  
$concatenate_end);
}

return 
$validate_if_not_empty;
}
?>

Signaalien käsittelyä Javalla

Tämä javaluokka on tehty erään signaalinkäsittelyn kurssin harjoitustyöksi. Se hyödyntää ImageJ kirjastoa ja sen Canny Edge Detector liitännäistä etsimään ylöspäin osoittavan sormen pään koordinaatit valokuvasta referenssikuvan avulla.


import ij.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.process.*;
import java.io.*;

/**
* Asks for an input image and finds the best match for a known
* reference image on it.

* If multiple matches found, picks the vertically highest one.
*
* @author Olli Ketola
* @version 23.05.2017
*/
public class Reference_Locator implements PlugIn
{
//initialization
private ImagePlus sourceImage;
private ImagePlus referenceImage;
private ImagePlus workingImage;    

/**
 * ImageJ plugin method that implements the reference image locator.
 * 
 * @param   arg     The brightness treshold used to recognize best
 * matches in the convolution.
 */
public void run(String arg)
{
    
    //opens a fixed test image as the source image, commented out
    //String sourcePath =
    //new File(".").getAbsolutePath()+"/test_scaled.JPG";
    //sourceImage=IJ.openImage(sourcePath);
    
    //asks the user for the source image.
    sourceImage=IJ.openImage();
    
    //opens a fixed reference image from known path
    String refPath = new File(".").getAbsolutePath()+"/ri.JPG";
    referenceImage=IJ.openImage(refPath);
    
    //clones the source to the workingImage
    workingImage = (ImagePlus)sourceImage.clone();
    
    //Sets up the canny edge detector and it's values.
    Canny_Edge_Detector canny = new Canny_Edge_Detector();
    canny.setLowThreshold((float)4);
    canny.setHighThreshold((float)6);
    canny.setGaussianKernelWidth(3);
    
    //edge detects the working image.
    workingImage = canny.process(workingImage);
                    
    //edge detects the reference image.
    referenceImage = canny.process(referenceImage);
    
    //converts the image processors to floats        
    ImageProcessor workingProcessor = workingImage.getProcessor();  
    ImageProcessor referenceProcessor = referenceImage.getProcessor();
    workingProcessor = workingProcessor.convertToFloat();
    referenceProcessor = referenceProcessor.convertToFloat();
            
    //gets the pixel arrays
    float[] workingPix = (float[])workingProcessor.getPixels();
    float[] referencePix = (float[])referenceProcessor.getPixels();
    
    //calculates dimensions
    int workH=workingImage.getHeight();
    int workW=workingImage.getWidth();        
    int refH=referenceImage.getHeight();
    int refW=referenceImage.getWidth();
    int refHoff=(int)refH/2;
    int refWoff=(int)refW/2;       
    
    //convolutes, tresholds, normalizes
    
    //intializes the variables used in the detection
    float minTreshold= Float.parseFloat(arg);
    int khA;    //used to store the kernel height index during convolution
    int kwA;    //same but for kernel width
    int whA;    //and working image height
    int wwA;    //and width
    float kA;   //used to store the value of the current kernel pixel
    float wA;   //and working image pixel
    
    //initializes the pixel arrays and processors for the convolution
    //and tresholded convolution images
    float[] convolutedPix = new float[workH*workW];
    float[] tresholdedPix = new float[workH*workW];
    ImageProcessor convolutedProcessor = new FloatProcessor(workW,workH);
    ImageProcessor tresholdedProcessor = new FloatProcessor(workW,workH);
    
    //initializes the variables used to save the fingertip coordinates
    int tipHmin=workH;
    int tipWatHmin=0;
    
    //sums the reference for normalization purposes
    float nSum=(float)0;
    for(int r=0;r<referencePix.length;r++){nSum=nSum+referencePix[r];}
    if(nSum==0)
        {
            nSum = (float)1;
        }
    
    //loops throuch the working image
    for(int h=0; h<workH;h++)
    {
        for(int w=0;w<workW;w++)
        {
            //convolutes the current working image pixel
            
            //initializes the convolution sum for this pixel
            float sum=(float)0;
            for(int kh = -refHoff;kh<refHoff;kh++)
            {
                for(int kw = -refWoff;kw<refWoff;kw++)
                {
                    //calculates convolution indices
                    khA = kh;
                    kwA = kw;
                    whA = h-kh;
                    wwA = w-kw;
                    //implements zero padding by checking that convolution
                    //indices don't go beyond array indices and setting the
                    //pixel values used in the convolution to 0 if they do
                    if((khA < 0 || refH <= khA)||(kwA < 0 || refW <= kwA))
                    {kA = (float)0;}
                    else
                    {kA = (float)(referencePix[khA*(refW)+kwA]);}
                    if((whA < 0 || workH <= whA)||(wwA < 0 || workW <= wwA))
                    {wA = (float)0;}
                    else
                    {wA = (float)(workingPix[whA*(workW)+wwA]);}
                    
                    //calculates the convolution sum
                    sum=(sum+(kA*wA));
                }
            }
                            
            //normalizes, tresholds & sets the tip coordinates to the
            //first (and thus highest on the image) matching pixel
            //from the tresholded convolution image
            convolutedPix[h*(workW)+w]=(float)((1/nSum)*sum);
            if(convolutedPix[h*(workW)+w]>=minTreshold)
            {
                tresholdedPix[h*(workW)+w]=convolutedPix[h*(workW)+w];                    
                if(h<tipHmin)
                {
                    tipHmin=h;
                    tipWatHmin=w;
                }                    
            }
            
        }
    }
    
    //redundant tip position coordinates
    int tipY = tipHmin;
    int tipX = tipWatHmin;
            
    //draws a plus sign at the tip coordinates on the working image
    for(int i=tipY-5;i<tipY+5;i++)
    {if(i>=0 && i<workH){workingPix[i*workW+tipX]=50;}}
    
    for(int i=tipX-5;i<tipX+5;i++)
    {if(i>=0 && i<workW){workingPix[tipY*workW+i]=50;}}
    
    //draws the quadrant dividing lines to the working image
    int Yhalf=(int)workH/2;
    int Xhalf=(int)workW/2;                
    for(int i=0;i<workH;i++){workingPix[i*workW+Xhalf]=50;}
    for(int i=0;i<workW;i++){workingPix[Yhalf*workW+i]=50;}
    
    //prints the found fingertip location to the system out stream
    if(tipY<Yhalf && tipX<Xhalf){System.out.println("Vasen yläkvadrantti.");}
    if(tipY<Yhalf && tipX>Xhalf){System.out.println("Oikea yläkvadrantti.");}
    if(tipY>Yhalf && tipX<Xhalf){System.out.println("Vasen alakvadrantti.");}
    if(tipY>Yhalf && tipX>Xhalf){System.out.println("Oikea alakvadrantti.");}
    
    //replaces the image processors with the modified ones
    workingProcessor.setPixels(workingPix);
    workingImage = new ImagePlus("WorkingImage",workingProcessor);
    
    convolutedProcessor.setPixels(convolutedPix);
    ImagePlus convolutedImage=
    new ImagePlus("Convolution",convolutedProcessor);
    
    tresholdedProcessor.setPixels(tresholdedPix);
    ImagePlus tresholdedImage=
    new ImagePlus("Tresholding",tresholdedProcessor);
    
    //updates and draws the images.     
    referenceImage.updateAndDraw();
    referenceImage.show();
    convolutedImage.updateAndDraw();
    convolutedImage.show();
    tresholdedImage.updateAndDraw();
    tresholdedImage.show();    
    workingImage.updateAndDraw();
    workingImage.show();  
    
}
}

Ohjelmalle syötetty testikuva. Valokuvamallina toimi harjoitusryhmäni jäsen Jussi Kaas.


Referenssikuva, jota käytettiin sormenpään löytämiseen testikuvasta.


Testikuvasta canny edge detectorilla tunnistetut reunaviivat, sekä ohjelman sen päälle piirtämät kvadranttiviivat ja tunnistettu sormenpään sijainti.


Referenssikuvasta tunnistetut reunaviivat.


Testi ja referenssikuvien reunaviivaversioiden konvoluutio.


Kynnysarvon ylittävät valkoiset pikselit konvoluutiokuvasta, kuvassa korkeimmalla olevan mustaa kirkkaamman pikselin koordinaatit valitaan sormenpään koordinaatiksi, tällä ajokerralla on käytetty kynnysarvoa 10.


Sumeaa logiikkaa Xcosilla

Tämä raportti, jonka aiheena on sumean logiikan hyödyntäminen piensähköverkkojen hallinnassa, on kirjoitettu harjoitustyönä eräälle sumean laskennan kurssille. Tämä Zcos tiedosto sisältää harjoitustyöhön toteutetun Scilab/Xcos simulaation.



CAD-mallinnus

Analoginen Laskin

Tämä 3D tulostettavaksi suunniteltu analoginen laskin sisältää helmitaulun, kiekkomaisen liukulaskimen, kynäkotelon taulutussille saranassa, sekä ruudukoita muistiinpanoille. Laite on mallinnettu openSCAD ohjelmointikielellä. Liukulaskimen asteikoiden suunnittelu ja ohjelmointi on toistaiseksi vielä kesken.

Koska malli on alunperin suunniteltu ennen kuin hankin oman 3D-tulostimen, se vaatii osittaista uudelleen suunnittelua sen sovittamiseksi tulostimeni tulostusalaan, sekä liiallisten ulkonemien poistamiseksi. Laitteen lähdekoodi on suunniteltu parametriseksi, mahdollistaen sen mittasuhteiden helpon muokkaamisen.


Mökin Pienoismalli

Tämä mökin pienoismalli on ohjelmoitu openSCAD:illa. Se on tulostettu valkoiselle PLA-muoville Monoprice Mini Delta 3D-tulostimella. Mökin huonekalut ovat irtonaisia, ja ikkunasäleikköjen välisiin koloihin voi sijoittaa pienet paperin tai kalvon palat simuloimaan ikkunaruutuja. Kattopalaa ei ole vielä suunniteltu ja ylempää seinäpalaa ei ole vielä tulostettu.


Pienoismallihahmo

Tämä fantasia-aiheinen pienoismalli on ohjelmoitu openSCAD:illa. Se on tulostettu valkoiselle PLA-muoville Monoprice Mini Delta 3D-tulostimella.


Laserleikkaus

Liukulaskin

Tämä läpinäkyvä laskukiekko on halkaisijaltaan 12 cm. Se on laserleikattu Vaasan Hacklab askarteluverstaalla 3 mm paksusta läpinäkyvästä akryylilevystä. Kiekko perustuu C. Kankelborgin liukulaskimeen, jonka suunnitelmia on muokattu paperille tulostettavasta muodosta paremmin laserleikattavaan muotoon sopivaksi.


Kansalaisareena

Seuraavat infograafit vapaaehtoistyöhön liittyvistä tilastoista on tehty vapaaehtoistyönä kansalaisareenan käyttöön loppukeväästä 2018. Ne on tehty Inkscape-ohjelmalla. Esitetyt tilastot ja kansalaisareenan logo on saatu asiakkaalta.

Jupiter-Säätiö

Seuraavat esitteet on tehty Jupiter-säätiön käyttöön kevään 2015 aikana.

Cafe Aukioloajat

Tämä yksipuolinen A3 kokoinen kyltti on tehty CorelDraw X6:lla soveltaen Jupiter-säätiön graafista ohjeistusta. Teksti on saatu asiakkaalta.


Startti Esite

Tämä yksipuolinen A4 kokoinen esite on tehty CorelDraw X6:lla soveltaen Jupiter-säätiön graafista ohjeistusta. Teksti ja valokuvamateriaali on saatu asiakkaalta.


Kierrätysmyymälän Esite

Tämä kaksipuolinen A5 kokoinen mainos on tehty CorelDraw X6:lla soveltaen Jupiter-säätiön graafista ohjeistusta. Teksti on saatu asiakkaalta.


Autofix Mainos

Tämä kaksipuolinen A5 kokoinen mainos on tehty CorelDraw X6:lla soveltaen Jupiter-säätiön graafista ohjeistusta. Teksti on saatu asiakkaalta.


Rekkapesu Hinnasto

Tämä kaksipuolinen A4 kokoinen mainos on tehty CorelDraw X6:lla soveltaen Jupiter-säätiön graafista ohjeistusta. Teksti on saatu asiakkaalta.