As I've commented already, this question made me curious, and the problem looks simple enough that it should be possible to solve it with a relatively simple algorithm.
TL;DR: Here's the JS fiddle that shows how the tiling of a rectangular area looks: https://jsfiddle.net/hrj8u3p5/29

Step 1: Cleaning up the problem statement
Your problem statement contains some apparent inaccuracies, some areas that can be improved to get to the core, and some difficult and probably not so important constraints that we're going to ignore for a first solution attempt.
Units of measurement
Your maximum tile size is certainly not 900x600 cm. That would be around 30x20 ft, a somewhat unwieldy size for a single tile. I suppose those are mm, not cm, which makes the biggest tile 3x2 ft, still a bit big but plausible. Anyway, that does not matter for the algorithm, due to the second point:
Removing unnecessary detail
If you look at the tile sizes, you quickly see that they have a largest common denominator of 150. Divide your sizes by that number to achieve small integer sizes, which are much easier to deal with: 6x4, 5x4, 4x4, 4x3, 3x3, 4x2, 3x2, 2x2. Note that the absence of a 1x1 tile makes the algorithm a little complicated, as it needs to do a bit of lookahead to avoid getting into an inresolvable situation.
Ignoring (possibly unimportant) constraints
Integrating the complex boundary of your floor plan into an algorithm would complicate it significantly. Instead, I propose to tile a rectangular area that can be overlaid with the actual floor plan to see which areas actually need to be tiled.
The requirement to keep cut-off residue to a minimum is likely very hard to satisfy, so I'd propose to cut tiles arbitrarily and re-use the cut-off parts if possible. Tilers do that anyway when necessary. In some cases, they might replace a tile proposed by the algorithm with one that fits with less waste, but I'd leave that to their discretion.
Step 2: Algorithm outline
As a first step, I'd choose a very simple "greedy" algorithm without backtracking. Backtracking could possible lead to somewhat better looking results and a simplification of the lookahead checks, but it would make the algorithm more complex.
Tiling in rows
We're working along rows, trying to fill them with random tiles while observing the aesthetical constraints if possible.
Hard and soft constraints
I've grouped the constraints into hard and soft to enable the algorithm to always find a solution, even if it does not look perfect. Hard constraints are those that are required to make a tiling possible at all, while the aesthetic constraints are soft and may be broken if otherwise a tile cannot be placed.
Possible improvements
A limited backtracking feature could help to avoid some aestetic constraint violations in the last row, but it's not clear whether the additional effort would be justified.
Step 3: The code
The javascript code at https://jsfiddle.net/hrj8u3p5/29 renders a 60x60 area using random tile colors and placements. To keep it available in case the JS fiddle expires, I've included it here:
HTML
<html>
<head>
<title>Tiling</title>
</head>
<body>
<canvas id="canvas" width="601" height="601"></canvas>
</body>
</html>
Javascript
let c = document.getElementById("canvas");
let ctx = c.getContext("2d");
let scale = 10;
function rnd(min, max) {
return Math.floor(Math.random()*(max-min))+min;
}
function rndColor() {
return "rgb(" + rnd(80,200) + "," + rnd(80,200) + "," + rnd(80,200) + ")";
}
function shuffle(arr) {
arr.sort(() => Math.random() - 0.5);
}
class Tile {
constructor(column, row, width, height, color) {
this.column = column;
this.row = row;
this.width = width;
this.height = height;
this.color = color
}
renderOn(ctx) {
ctx.fillStyle = this.color;
let x = this.column*scale;
let y = this.row*scale;
let w = this.width*scale;
let h = this.height*scale;
ctx.fillRect(x+1, y+1, w-1, h-1)
}
is_similar(other) {
return this.width == other.width && this.height == other.height;
}
is_left_similar(other) {
return other.row == this.row && this.is_similar(other);
}
is_top_similar(other) {
return other.column == this.column && this.is_similar(other);
}
transposed() {
return new Tile(this.column, this.row, this.height, this.width, this.color);
}
placed_at(columnIndex, rowIndex) {
return new Tile(columnIndex, rowIndex, this.width, this.height, this.color);
}
}
class Floor {
constructor(width, height) {
this.width = width;
this.height = height;
this.reset();
this.tileTypes = [];
let sizes = [[2,2],[3,2],[4,2],[3,3],[4,3],[4,4],[5,4],[6,4]];
for (let s of sizes) {
let tile = new Tile(0,0,s[0], s[1], rndColor());
this.tileTypes.push(tile);
this.tileTypes.push(tile.transposed());
}
}
reset() {
this.rows = [];
this.tiles = [];
for (let i = 0; i < this.height; i++) {
this.rows.push(new Array(this.width))
}
}
place(tile) {
this.tiles.push(tile);
for (let rowIndex = tile.row; rowIndex < tile.row+tile.height; rowIndex++) {
for (let columnIndex = tile.column; columnIndex < tile.column+tile.width; columnIndex++) {
this.rows[rowIndex][columnIndex] = tile;
}
}
}
// return whether we can legally place a tile
canPlace(tile) {
if (tile.column + tile.width > this.width) {
return false;
}
if (tile.column + tile.width + 1 == this.width) {
return false;
}
if (tile.row + tile.height > this.height) {
return false;
}
if (tile.row + tile.height + 1 == this.height) {
return false;
}
let row = this.rows[tile.row];
if (row[tile.column+tile.width] == undefined && row[tile.column+tile.width+1] !== undefined) {
return false;
}
for (let r = tile.row; r < tile.row + tile.height; r++) {
for (let c = tile.column; c < tile.column + tile.width; c++)
if (this.rows[r][c] !== undefined) {
return false
}
}
return true;
}
// return whether we should aesthetically place a tile (legality should already be established)
shouldPlace(tile) {
if (tile.column > 0 && tile.is_left_similar(this.rows[tile.row][tile.column-1])) {
return false;
}
if (tile.row > 0) {
if (tile.is_top_similar(this.rows[tile.row-1][tile.column])) {
return false;
}
let nextColumn = tile.column + tile.width;
if (nextColumn < this.width) {
let topRight = this.rows[tile.row-1][nextColumn];
if (topRight.column == nextColumn && topRight.row + topRight.height == tile.row) {
return false;
}
}
}
return true;
}
placeAny(columnIndex, rowIndex) {
shuffle(this.tileTypes);
let possible = this.tileTypes
.map(x => x.placed_at(columnIndex, rowIndex))
.filter(x => this.canPlace(x));
for (let tile of possible) {
if (this.shouldPlace(tile)) {
this.place(tile);
return
}
}
if (possible.length > 0) {
this.place(possible[0]);
return;
}
console.log("could not place tile at row " + rowIndex + " column " + columnIndex);
}
fillRow(rowIndex) {
let row = this.rows[rowIndex];
for (let c = 0; c < this.width; c++) {
if (row[c] === undefined) {
this.placeAny(c, rowIndex);
}
}
}
fill() {
for (let r = 0; r < this.height; r++) {
this.fillRow(r);
}
}
renderOn(ctx) {
ctx.fillStyle = "#000";
ctx.fillRect(0,0,601,601);
for (let t of this.tiles) {
t.renderOn(ctx)
}
}
}
var floor = new Floor(60,60);
floor.fill();
floor.renderOn(ctx);