bx-865-apps #1
|
@ -1533,6 +1533,54 @@
|
||||||
"detail": "django.db",
|
"detail": "django.db",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "migrations",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "models",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "migrations",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "models",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "migrations",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "models",
|
||||||
|
"importPath": "django.db",
|
||||||
|
"description": "django.db",
|
||||||
|
"isExtraImport": true,
|
||||||
|
"detail": "django.db",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "models",
|
"label": "models",
|
||||||
"importPath": "django.db",
|
"importPath": "django.db",
|
||||||
|
@ -8242,6 +8290,33 @@
|
||||||
"detail": "back.api.migrations.0001_initial",
|
"detail": "back.api.migrations.0001_initial",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Migration",
|
||||||
|
"kind": 6,
|
||||||
|
"importPath": "back.api.migrations.0002_floorplan_d_border_floorplan_d_size_floorplan_paths",
|
||||||
|
"description": "back.api.migrations.0002_floorplan_d_border_floorplan_d_size_floorplan_paths",
|
||||||
|
"peekOfCode": "class Migration(migrations.Migration):\n dependencies = [\n ('api', '0001_initial'),\n ]\n operations = [\n migrations.AddField(\n model_name='floorplan',\n name='d_border',\n field=models.IntegerField(default=1),\n preserve_default=False,",
|
||||||
|
"detail": "back.api.migrations.0002_floorplan_d_border_floorplan_d_size_floorplan_paths",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Migration",
|
||||||
|
"kind": 6,
|
||||||
|
"importPath": "back.api.migrations.0003_alter_floorplan_paths",
|
||||||
|
"description": "back.api.migrations.0003_alter_floorplan_paths",
|
||||||
|
"peekOfCode": "class Migration(migrations.Migration):\n dependencies = [\n ('api', '0002_floorplan_d_border_floorplan_d_size_floorplan_paths'),\n ]\n operations = [\n migrations.AlterField(\n model_name='floorplan',\n name='paths',\n field=models.TextField(),\n ),",
|
||||||
|
"detail": "back.api.migrations.0003_alter_floorplan_paths",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Migration",
|
||||||
|
"kind": 6,
|
||||||
|
"importPath": "back.api.migrations.0004_alter_floorplan_d_border_alter_floorplan_d_size",
|
||||||
|
"description": "back.api.migrations.0004_alter_floorplan_d_border_alter_floorplan_d_size",
|
||||||
|
"peekOfCode": "class Migration(migrations.Migration):\n dependencies = [\n ('api', '0003_alter_floorplan_paths'),\n ]\n operations = [\n migrations.AlterField(\n model_name='floorplan',\n name='d_border',\n field=models.IntegerField(blank=True, null=True),\n ),",
|
||||||
|
"detail": "back.api.migrations.0004_alter_floorplan_d_border_alter_floorplan_d_size",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "ApiConfig",
|
"label": "ApiConfig",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
|
@ -8256,7 +8331,7 @@
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"importPath": "back.api.models",
|
"importPath": "back.api.models",
|
||||||
"description": "back.api.models",
|
"description": "back.api.models",
|
||||||
"peekOfCode": "class Product(models.Model):\n title = models.CharField(max_length=100)\n description = models.TextField(default=None, null=True)\n model3d = models.FileField(\n default=None, blank=True, null=True, upload_to=\"files\"\n )\n image1 = models.ImageField(\n default=None, blank=True, null=True, upload_to=\"files\"\n )\n image2 = models.ImageField(",
|
"peekOfCode": "class Product(models.Model):\n title = models.CharField(max_length=100)\n description = models.TextField(default=None, null=True)\n model3d = models.FileField(default=None, blank=True, null=True, upload_to=\"files\")\n image1 = models.ImageField(default=None, blank=True, null=True, upload_to=\"files\")\n image2 = models.ImageField(default=None, blank=True, null=True, upload_to=\"files\")\n image3 = models.ImageField(default=None, blank=True, null=True, upload_to=\"files\")\n def __str__(self):\n return self.title\nclass Floorplan(models.Model):",
|
||||||
"detail": "back.api.models",
|
"detail": "back.api.models",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8265,7 +8340,7 @@
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"importPath": "back.api.models",
|
"importPath": "back.api.models",
|
||||||
"description": "back.api.models",
|
"description": "back.api.models",
|
||||||
"peekOfCode": "class Floorplan(models.Model):\n title = models.CharField(max_length=200)\n np_field = models.TextField()",
|
"peekOfCode": "class Floorplan(models.Model):\n title = models.CharField(max_length=200)\n np_field = models.TextField()\n d_size = models.IntegerField(null=True, blank=True)\n d_border = models.IntegerField(null=True, blank=True)\n paths = models.TextField()",
|
||||||
"detail": "back.api.models",
|
"detail": "back.api.models",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8283,7 +8358,25 @@
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"importPath": "back.api.serializers",
|
"importPath": "back.api.serializers",
|
||||||
"description": "back.api.serializers",
|
"description": "back.api.serializers",
|
||||||
"peekOfCode": "class FloorplanSerializer(serializers.Serializer):\n title = serializers.CharField(max_length=200)\n np_field = serializers.CharField()\n def create(self,validated_data):\n return Floorplan.objects.create(**validated_data)",
|
"peekOfCode": "class FloorplanSerializer(serializers.ModelSerializer):\n class Meta:\n model = Floorplan\n fields = [\n \"id\",\n \"title\",\n \"np_field\",\n \"d_border\",\n \"d_size\",\n \"paths\"",
|
||||||
|
"detail": "back.api.serializers",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "FloorplanListSerializer",
|
||||||
|
"kind": 6,
|
||||||
|
"importPath": "back.api.serializers",
|
||||||
|
"description": "back.api.serializers",
|
||||||
|
"peekOfCode": "class FloorplanListSerializer(serializers.ModelSerializer):\n class Meta:\n model = Floorplan\n fields = [\n \"id\",\n \"title\",\n ]",
|
||||||
|
"detail": "back.api.serializers",
|
||||||
|
"documentation": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "logger",
|
||||||
|
"kind": 5,
|
||||||
|
"importPath": "back.api.serializers",
|
||||||
|
"description": "back.api.serializers",
|
||||||
|
"peekOfCode": "logger = logging.getLogger(\"root\")\nclass ProductSerializer(serializers.HyperlinkedModelSerializer):\n class Meta:\n model = Product\n fields = [\n \"id\",\n \"title\",\n \"description\",\n \"model3d\",\n \"image1\",",
|
||||||
"detail": "back.api.serializers",
|
"detail": "back.api.serializers",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8319,7 +8412,7 @@
|
||||||
"kind": 2,
|
"kind": 2,
|
||||||
"importPath": "back.api.tracer",
|
"importPath": "back.api.tracer",
|
||||||
"description": "back.api.tracer",
|
"description": "back.api.tracer",
|
||||||
"peekOfCode": "def parse_image(img):\n (img_h, img_w) = img.shape[:2]\n t = 1200\n w = t\n h = int((img_h / img_w) * t)\n img = cv2.resize(img, (w, h))\n gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n gray = 255 - gray\n gray = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)[1]\n gray = cv2.blur(gray, (10, 5))",
|
"peekOfCode": "def parse_image(img):\n (img_h, img_w) = img.shape[:2]\n t = 1920\n w = t\n h = int((img_h / img_w) * t)\n img = cv2.resize(img, (w, h))\n gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n gray = 255 - gray\n gray = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)[1]\n gray = cv2.blur(gray, (10, 5))",
|
||||||
"detail": "back.api.tracer",
|
"detail": "back.api.tracer",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8337,7 +8430,7 @@
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"importPath": "back.api.views",
|
"importPath": "back.api.views",
|
||||||
"description": "back.api.views",
|
"description": "back.api.views",
|
||||||
"peekOfCode": "class FloorplanView(APIView):\n parser_classes = (MultiPartParser,)\n def post(self, request):\n try:\n file = request.FILES[\"demo\"]\n logger.info(file.__dict__)\n res = parse_image(read_image(file.read()))\n serializer = FloorplanSerializer(\n data={\"title\": file.name, \"np_field\": res[\"b64\"].decode()}\n )",
|
"peekOfCode": "class FloorplanView(APIView):\n parser_classes = (MultiPartParser,)\n def post(self, request):\n try:\n file = request.FILES[\"demo\"]\n logger.info(file.__dict__)\n res = parse_image(read_image(file.read()))\n serializer = FloorplanSerializer(\n data={\n \"title\": file.name,",
|
||||||
"detail": "back.api.views",
|
"detail": "back.api.views",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8553,7 +8646,7 @@
|
||||||
"kind": 5,
|
"kind": 5,
|
||||||
"importPath": "back.back.urls",
|
"importPath": "back.back.urls",
|
||||||
"description": "back.back.urls",
|
"description": "back.back.urls",
|
||||||
"peekOfCode": "urlpatterns = [\n path(\"admin/\", admin.site.urls),\n path(\"api/products\", csrf_exempt(views.Products.as_view())),\n path(\"api/floorplan\", csrf_exempt(views.FloorplanView.as_view())),\n] + static('/files', document_root='files')",
|
"peekOfCode": "urlpatterns = [\n path(\"admin/\", admin.site.urls),\n path(\"api/products\", csrf_exempt(views.Products.as_view())),\n path(\"api/floorplan/\", csrf_exempt(views.FloorplanView.as_view())),\n path(\"api/floorplan/<str:id>\", csrf_exempt(views.FloorplanView.as_view())),\n] + static('/files', document_root='files')",
|
||||||
"detail": "back.back.urls",
|
"detail": "back.back.urls",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,3 +33,6 @@
|
||||||
@apply bg-surface-100 dark:bg-[rgba(255,255,255,0.03)]
|
@apply bg-surface-100 dark:bg-[rgba(255,255,255,0.03)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-pc-section="list"] {
|
||||||
|
@apply bg-slate-50
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
export function* chunks(arr: any, n: number) {
|
export function* chunks<T>(arr: T[], n: number): Generator<T[], void> {
|
||||||
for (let i = 0; i < arr.length; i += n) {
|
for (let i = 0; i < arr.length; i += n) {
|
||||||
yield arr.slice(i, i + n);
|
yield arr.slice(i, i + n);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,16 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FileUploadUploadEvent } from '~/node_modules/primevue/fileupload/FileUpload.d.ts'
|
import type { FileUploadUploadEvent } from '~/node_modules/primevue/fileupload/FileUpload.d.ts'
|
||||||
import PF, { Grid } from 'pathfinding'
|
import PF, { Grid } from 'pathfinding'
|
||||||
|
import { chunks } from '~/helpers';
|
||||||
|
|
||||||
interface SvgContext {
|
|
||||||
array: number[][],
|
|
||||||
paths: string[],
|
|
||||||
width: number,
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const plan: Ref<SvgContext | {}> = useState('plan', () => ({}));
|
const point_array = ref<number[][]>()
|
||||||
|
const paths_array = ref<{ path: string, unwalkable: boolean, x: number, y: number }[]>()
|
||||||
const loading = ref<boolean>(false)
|
const loading = ref<boolean>(false)
|
||||||
|
|
||||||
const canvasElement: Ref<HTMLCanvasElement | undefined> = ref();
|
const canvasElement: Ref<HTMLCanvasElement | undefined> = ref();
|
||||||
|
@ -21,7 +16,14 @@ const startPoint = ref<number[]>()
|
||||||
const endPoint = ref<number[]>()
|
const endPoint = ref<number[]>()
|
||||||
const startToEndPath = ref()
|
const startToEndPath = ref()
|
||||||
|
|
||||||
const chunkSize = 2
|
const selectFile = ref()
|
||||||
|
const loadingFile = ref(false)
|
||||||
|
const files = ref([])
|
||||||
|
|
||||||
|
const cw = 1920
|
||||||
|
const ch = 800
|
||||||
|
|
||||||
|
const nextFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
|
||||||
const beforeUpload = () => {
|
const beforeUpload = () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
@ -30,91 +32,182 @@ const beforeUpload = () => {
|
||||||
const onUpload = (event: FileUploadUploadEvent) => {
|
const onUpload = (event: FileUploadUploadEvent) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.xhr.response)
|
const data = JSON.parse(event.xhr.response)
|
||||||
plan.value = data.response
|
point_array.value = data.response.np_field
|
||||||
|
paths_array.value = sampling_data(data.response.np_field, 8, 20)
|
||||||
|
|
||||||
newDraw()
|
newDraw()
|
||||||
loading.value = false
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
loading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const selectFileEvent = async () => {
|
||||||
|
loadingFile.value = true
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${config.public.apiBase}/api/floorplan/${selectFile.value}`)
|
||||||
|
const data = await res.json()
|
||||||
|
point_array.value = data.np_field
|
||||||
|
paths_array.value = sampling_data(data.np_field, 8, 20)
|
||||||
|
|
||||||
const newDraw = () => {
|
newDraw()
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
loadingFile.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampling_data = (np_field: number[][], chunkSize: number, threshold: number) => {
|
||||||
|
const prepared_array = [...chunks(np_field, chunkSize)].map(line => {
|
||||||
|
const line_data = [] as any[][]
|
||||||
|
line.map((item: any) => [...chunks(item, chunkSize)]).map((item) => {
|
||||||
|
item.map((one_line, k) => {
|
||||||
|
if (!line_data[k]) line_data[k] = []
|
||||||
|
line_data[k].push(...one_line)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return line_data.map(el => {
|
||||||
|
return el.filter(e => e > 0).length > threshold ? 1 : 0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const res: any[] = []
|
||||||
|
prepared_array.forEach((line, indexY) => {
|
||||||
|
line.forEach((point, indexX) => {
|
||||||
|
const targetX = indexX * chunkSize
|
||||||
|
const targetY = indexY * chunkSize
|
||||||
|
res.push({
|
||||||
|
path: `M${targetX} ${targetY} ${targetX + chunkSize} ${targetY} ${targetX + chunkSize} ${targetY + chunkSize} ${targetX} ${targetY + chunkSize}Z`,
|
||||||
|
unwalkable: !!point,
|
||||||
|
x: indexX,
|
||||||
|
y: indexY,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
const newDraw = async () => {
|
||||||
startPoint.value = undefined
|
startPoint.value = undefined
|
||||||
endPoint.value = undefined
|
endPoint.value = undefined
|
||||||
startToEndPath.value = undefined
|
startToEndPath.value = undefined
|
||||||
|
|
||||||
context.value = canvasElement.value?.getContext('2d') || undefined;
|
context.value = canvasElement.value?.getContext('2d') || undefined;
|
||||||
const lines = (plan.value as SvgContext).array
|
const lines = point_array.value
|
||||||
lines.forEach((line, indexY) => {
|
if (!lines) return;
|
||||||
line.forEach((point, indexX) => {
|
|
||||||
if (canvasElement.value && context.value) {
|
|
||||||
context.value.fillStyle = point > 0 ? 'red' : 'WHITE'
|
|
||||||
context.value.fillRect(indexX, indexY, 1, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// const t = [...chunks(lines, chunkSize)].map(y => y.map(x => x.some(i => i > 0) > 0 ? 1 : 0))
|
context.value?.clearRect(0, 0, cw, ch)
|
||||||
// console.log(t)
|
await nextFrame()
|
||||||
|
|
||||||
grid.value = new PF.Grid((plan.value as SvgContext).array.map(y => y.map(x => x > 0 ? 1 : 0)))
|
for (let indexY = 0; indexY < lines.length; indexY++) {
|
||||||
|
const line = lines[indexY];
|
||||||
|
|
||||||
|
const drawLine = () => {
|
||||||
|
line.forEach((point, indexX) => {
|
||||||
|
if (canvasElement.value && context.value && point > 0) {
|
||||||
|
context.value.fillStyle = 'crimson'
|
||||||
|
context.value.fillRect(indexX, indexY, 1, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
drawLine()
|
||||||
|
await nextFrame()
|
||||||
|
}
|
||||||
|
grid.value = new PF.Grid(lines.map(y => y.map(x => x > 0 ? 1 : 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPoint = (event: MouseEvent) => {
|
|
||||||
|
const toggleBlock = (name: string) => {
|
||||||
|
if (openBlocks.value.includes(name)) {
|
||||||
|
openBlocks.value.splice(openBlocks.value.indexOf(name), 1)
|
||||||
|
} else {
|
||||||
|
openBlocks.value.push(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isBlockOpen = (name: string) => {
|
||||||
|
return openBlocks.value.includes(name)
|
||||||
|
}
|
||||||
|
const openBlocks = ref<string[]>([])
|
||||||
|
|
||||||
|
const fileBtnClick = async () => {
|
||||||
|
if (isBlockOpen('upload_file')) {
|
||||||
|
await loadFiles()
|
||||||
|
}
|
||||||
|
toggleBlock('upload_file')
|
||||||
|
}
|
||||||
|
const loadFiles = async () => {
|
||||||
try {
|
try {
|
||||||
if (!event.currentTarget) return
|
loadingFile.value = true
|
||||||
const target = (<HTMLCanvasElement>event.currentTarget)
|
const res = await fetch(`${config.public.apiBase}/api/floorplan/`)
|
||||||
const bound = target.getBoundingClientRect()
|
const data = await res.json()
|
||||||
|
files.value = data
|
||||||
const targetX = event.x - bound.x
|
|
||||||
const targetY = event.y - bound.y
|
|
||||||
|
|
||||||
const ctx = (context.value as CanvasRenderingContext2D)
|
|
||||||
|
|
||||||
if (!startPoint.value) {
|
|
||||||
startPoint.value = [targetX, targetY]
|
|
||||||
ctx.fillStyle = 'rgb(0,255,0,0.75)';
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(targetX, targetY, 10, 0, 2 * Math.PI);
|
|
||||||
ctx.fill();
|
|
||||||
} else
|
|
||||||
if (!endPoint.value) {
|
|
||||||
endPoint.value = [targetX, targetY]
|
|
||||||
ctx.fillStyle = 'rgb(0,0,255,0.75)';
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(targetX, targetY, 10, 0, 2 * Math.PI);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
const finder = new PF.DijkstraFinder();
|
|
||||||
|
|
||||||
startToEndPath.value = finder.findPath(
|
|
||||||
Math.round(startPoint.value[0]),
|
|
||||||
Math.round(startPoint.value[1]),
|
|
||||||
Math.round(endPoint.value[0]),
|
|
||||||
Math.round(endPoint.value[1]),
|
|
||||||
(grid.value as Grid)
|
|
||||||
);
|
|
||||||
startToEndPath.value.forEach((element: number[]) => {
|
|
||||||
ctx.fillRect(element[0], element[1], 1, 1)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
newDraw()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
|
loadingFile.value = false
|
||||||
}
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadFiles()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<Panel header="Загрузка файла">
|
<Panel header="Выбор пресета">
|
||||||
<FileUpload mode="basic" name="demo" :url="`${config.public.apiBase}/api/floorplan`" accept="image/*"
|
<div class="flex align-center gap-2">
|
||||||
|
<Dropdown v-model="selectFile" placeholder="Выберите файл" :options="files" optionLabel="title"
|
||||||
|
optionValue="id" :loading="loadingFile" :disabled="!files.length || loadingFile"
|
||||||
|
@change="selectFileEvent" />
|
||||||
|
<Button @click="fileBtnClick()">
|
||||||
|
<span style="display: contents;" v-if="isBlockOpen('upload_file')">
|
||||||
|
Выбрать план из загруженных
|
||||||
|
</span>
|
||||||
|
<span style="display: contents;" v-else>
|
||||||
|
Загрузить новый план
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
<Panel header="Загрузка файла" v-if="isBlockOpen('upload_file')">
|
||||||
|
<FileUpload mode="basic" name="demo" :url="`${config.public.apiBase}/api/floorplan/`" accept="image/*"
|
||||||
:maxFileSize="10000000" @upload="onUpload" @before-upload="beforeUpload" :auto="true"
|
:maxFileSize="10000000" @upload="onUpload" @before-upload="beforeUpload" :auto="true"
|
||||||
chooseLabel="Выберите файл" :disabled="loading" />
|
chooseLabel="Выберите файл" :disabled="loading" />
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel :header="loading ? `Идет отрисовка` : `Отрисовка из массива значений`">
|
|
||||||
<canvas ref="canvasElement" :width="1200" height="400" @click="setPoint"></canvas>
|
<Panel :header="loading ? `Идет отрисовка` : `Обработанный план здания`">
|
||||||
|
<div style=" max-width: 100%; overflow: auto; position: relative;">
|
||||||
|
<canvas ref="canvasElement" :width="cw" :height="ch" style="position: absolute; z-index: -1;"></canvas>
|
||||||
|
<svg ref="svgElement" :width="cw" :height="ch">
|
||||||
|
<path v-for="item in paths_array" :d="item.path" :class="[
|
||||||
|
{ 'unwalkable': item.unwalkable },
|
||||||
|
// { 'endPoint': (endPoint && item.x == endPoint.x && item.y == endPoint.y) },
|
||||||
|
// { 'startPoint': (startPoint && item.x == startPoint.x && item.y == startPoint.y) },
|
||||||
|
// { 'pathPoint': (startToEndPath && startToEndPath.find((el: number[]) => el[0] == item.x && el[1] == item.y)) },
|
||||||
|
]">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
svg path {
|
||||||
|
fill: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path:hover {
|
||||||
|
fill: red
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.unwalkable {
|
||||||
|
fill: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.endPoint {
|
||||||
|
fill: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.startPoint {
|
||||||
|
fill: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.pathPoint {
|
||||||
|
fill: gold;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,27 +1,23 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
description = models.TextField(default=None, null=True)
|
description = models.TextField(default=None, null=True)
|
||||||
model3d = models.FileField(
|
model3d = models.FileField(default=None, blank=True, null=True, upload_to="files")
|
||||||
default=None, blank=True, null=True, upload_to="files"
|
image1 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
||||||
)
|
image2 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
||||||
image1 = models.ImageField(
|
image3 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
||||||
default=None, blank=True, null=True, upload_to="files"
|
|
||||||
)
|
|
||||||
image2 = models.ImageField(
|
|
||||||
default=None, blank=True, null=True, upload_to="files"
|
|
||||||
)
|
|
||||||
image3 = models.ImageField(
|
|
||||||
default=None, blank=True, null=True, upload_to="files"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class Floorplan(models.Model):
|
class Floorplan(models.Model):
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
np_field = models.TextField()
|
np_field = models.TextField()
|
||||||
|
d_size = models.IntegerField(null=True, blank=True)
|
||||||
|
d_border = models.IntegerField(null=True, blank=True)
|
||||||
|
paths = models.TextField()
|
|
@ -1,6 +1,8 @@
|
||||||
from rest_framework import routers, serializers, viewsets
|
from rest_framework import routers, serializers, viewsets
|
||||||
from .models import Product, Floorplan
|
from .models import Product, Floorplan
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("root")
|
||||||
|
|
||||||
class ProductSerializer(serializers.HyperlinkedModelSerializer):
|
class ProductSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -16,9 +18,22 @@ class ProductSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FloorplanSerializer(serializers.Serializer):
|
class FloorplanSerializer(serializers.ModelSerializer):
|
||||||
title = serializers.CharField(max_length=200)
|
class Meta:
|
||||||
np_field = serializers.CharField()
|
model = Floorplan
|
||||||
|
fields = [
|
||||||
def create(self,validated_data):
|
"id",
|
||||||
return Floorplan.objects.create(**validated_data)
|
"title",
|
||||||
|
"np_field",
|
||||||
|
"d_border",
|
||||||
|
"d_size",
|
||||||
|
"paths"
|
||||||
|
]
|
||||||
|
|
||||||
|
class FloorplanListSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Floorplan
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
]
|
|
@ -31,7 +31,7 @@ def read_image(content: bytes) -> np.ndarray:
|
||||||
|
|
||||||
def parse_image(img):
|
def parse_image(img):
|
||||||
(img_h, img_w) = img.shape[:2]
|
(img_h, img_w) = img.shape[:2]
|
||||||
t = 1200
|
t = 1920
|
||||||
w = t
|
w = t
|
||||||
h = int((img_h / img_w) * t)
|
h = int((img_h / img_w) * t)
|
||||||
img = cv2.resize(img, (w, h))
|
img = cv2.resize(img, (w, h))
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
import json
|
||||||
from rest_framework.parsers import JSONParser, MultiPartParser
|
from rest_framework.parsers import JSONParser, MultiPartParser
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
from api.tracer import parse_image, read_image, numpy_zip_str_to_arr
|
from api.tracer import parse_image, read_image, numpy_zip_str_to_arr
|
||||||
from .serializers import FloorplanSerializer, ProductSerializer
|
from .serializers import FloorplanSerializer, FloorplanListSerializer, ProductSerializer
|
||||||
from .models import Floorplan, Product
|
from .models import Floorplan, Product
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -38,26 +38,43 @@ class FloorplanView(APIView):
|
||||||
logger.info(file.__dict__)
|
logger.info(file.__dict__)
|
||||||
res = parse_image(read_image(file.read()))
|
res = parse_image(read_image(file.read()))
|
||||||
serializer = FloorplanSerializer(
|
serializer = FloorplanSerializer(
|
||||||
data={"title": file.name, "np_field": res["b64"].decode()}
|
data={
|
||||||
|
"title": file.name,
|
||||||
|
"np_field": res["b64"].decode(),
|
||||||
|
"paths": json.dumps(res["paths"]),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
logger.info(res["b64"])
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
data={"response": {"array": res["array"]}}, status=201
|
data={
|
||||||
|
"response": {
|
||||||
|
"np_field": res["array"],
|
||||||
|
"paths": res["paths"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status=201,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
logger.info(serializer.errors)
|
||||||
return JsonResponse(serializer.errors, status=500)
|
return JsonResponse(serializer.errors, status=500)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request, id=None):
|
||||||
try:
|
try:
|
||||||
item = Floorplan.objects.last()
|
if id is not None:
|
||||||
serializer = FloorplanSerializer(item, many=False)
|
item = Floorplan.objects.get(id=id)
|
||||||
data = serializer.data
|
serializer = FloorplanSerializer(item, many=False)
|
||||||
data["np_field"] = numpy_zip_str_to_arr(data["np_field"])
|
data = serializer.data
|
||||||
return JsonResponse(data, safe=False)
|
data["np_field"] = numpy_zip_str_to_arr(data["np_field"])
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
else:
|
||||||
|
items = Floorplan.objects.only("id", "title").all()
|
||||||
|
serializer = FloorplanListSerializer(items, many=True)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
raise e
|
raise e
|
||||||
|
|
|
@ -23,5 +23,6 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/products", csrf_exempt(views.Products.as_view())),
|
path("api/products", csrf_exempt(views.Products.as_view())),
|
||||||
path("api/floorplan", csrf_exempt(views.FloorplanView.as_view())),
|
path("api/floorplan/", csrf_exempt(views.FloorplanView.as_view())),
|
||||||
|
path("api/floorplan/<str:id>", csrf_exempt(views.FloorplanView.as_view())),
|
||||||
] + static('/files', document_root='files')
|
] + static('/files', document_root='files')
|
||||||
|
|
|
@ -12,7 +12,7 @@ const canvasElement = ref();
|
||||||
const context = ref();
|
const context = ref();
|
||||||
|
|
||||||
const grid = ref<Grid>()
|
const grid = ref<Grid>()
|
||||||
const startPoint = ref<{ x: number, y: number }>({ x: 27, y: 38 })
|
const startPoint = ref<{ x: number, y: number }>({ x: 25, y: 40 })
|
||||||
const endPoint = ref<{ x: number, y: number }>()
|
const endPoint = ref<{ x: number, y: number }>()
|
||||||
const startToEndPath = ref()
|
const startToEndPath = ref()
|
||||||
|
|
||||||
|
@ -30,12 +30,26 @@ const newDraw = () => {
|
||||||
lines.forEach((line, indexY) => {
|
lines.forEach((line, indexY) => {
|
||||||
line.forEach((point, indexX) => {
|
line.forEach((point, indexX) => {
|
||||||
if (canvasElement.value && context.value) {
|
if (canvasElement.value && context.value) {
|
||||||
context.value.fillStyle = point > 0 ? 'red' : 'WHITE'
|
context.value.fillStyle = point > 0 ? 'purple' : 'WHITE'
|
||||||
context.value.fillRect(indexX, indexY, 1, 1)
|
context.value.fillRect(indexX, indexY, 1, 1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if (canvasElement.value && context.value) {
|
||||||
|
// context.value.clearRect(0, 0, canvasElement.value.width, canvasElement.value.height);
|
||||||
|
// if (!plan.value.hasOwnProperty('paths')) return
|
||||||
|
// (plan.value).paths.forEach((path: string) => {
|
||||||
|
// if (!context.value) return
|
||||||
|
// context.value.fillStyle = 'rgba(255, 0, 255, 0.25)'
|
||||||
|
// context.value.fill(new Path2D(path));
|
||||||
|
|
||||||
|
// context.value.strokeStyle = 'blue'
|
||||||
|
// context.value.stroke(new Path2D(path));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
const quantum_lines = plan.prepared_array
|
const quantum_lines = plan.prepared_array
|
||||||
quantum_lines.forEach((line, indexY) => {
|
quantum_lines.forEach((line, indexY) => {
|
||||||
line.forEach((point, indexX) => {
|
line.forEach((point, indexX) => {
|
||||||
|
@ -55,7 +69,7 @@ const newDraw = () => {
|
||||||
|
|
||||||
const findPath = async () => {
|
const findPath = async () => {
|
||||||
if (!endPoint.value) return
|
if (!endPoint.value) return
|
||||||
const localPath = finder.findPath(
|
const localPath = finder.findPath(
|
||||||
Math.round(startPoint.value.x),
|
Math.round(startPoint.value.x),
|
||||||
Math.round(startPoint.value.y),
|
Math.round(startPoint.value.y),
|
||||||
Math.round(endPoint.value.x),
|
Math.round(endPoint.value.x),
|
||||||
|
@ -75,11 +89,13 @@ onMounted(async () => {
|
||||||
await floorplan.getData()
|
await floorplan.getData()
|
||||||
newDraw()
|
newDraw()
|
||||||
})
|
})
|
||||||
|
const cw = 1920
|
||||||
|
const ch = 800
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="container" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
|
<div class="container" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
|
||||||
<canvas ref="canvasElement" width="1200" height="600"></canvas>
|
<canvas ref="canvasElement" :width="cw" :height="ch"></canvas>
|
||||||
<svg ref="svgElement" width="1200" height="600" style="position: absolute;">
|
<svg ref="svgElement" :width="cw" :height="ch" style="position: absolute;">
|
||||||
<path v-for="item in paths" :d="item.path" @click="setPointSvg(item)" :class="[
|
<path v-for="item in paths" :d="item.path" @click="setPointSvg(item)" :class="[
|
||||||
{ 'unwalkable': item.unwalkable },
|
{ 'unwalkable': item.unwalkable },
|
||||||
{ 'endPoint': (endPoint && item.x == endPoint.x && item.y == endPoint.y) },
|
{ 'endPoint': (endPoint && item.x == endPoint.x && item.y == endPoint.y) },
|
||||||
|
@ -100,7 +116,7 @@ svg path:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
svg path.unwalkable {
|
svg path.unwalkable {
|
||||||
fill: rgba(0, 0, 0, 0.5);
|
/* fill: rgba(0, 0, 0, 0.5); */
|
||||||
}
|
}
|
||||||
|
|
||||||
svg path.endPoint {
|
svg path.endPoint {
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const useFloorplanStore = defineStore('floorplan', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
title: undefined,
|
title: undefined,
|
||||||
chunkSize: 5,
|
chunkSize: 7,
|
||||||
np_array: [] as number[][],
|
np_array: [] as number[][],
|
||||||
prepared_array: [] as number[][]
|
prepared_array: [] as number[][]
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export const useFloorplanStore = defineStore('floorplan', {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return line_data.map(el => {
|
return line_data.map(el => {
|
||||||
return el.filter(e => e > 0).length > 10 ? 1 : 0
|
return el.filter(e => e > 0).length > 20 ? 1 : 0
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
Loading…
Reference in New Issue