Commit 50eeb101 authored by insun park's avatar insun park
Browse files

refactor: 프로젝트 구조 개편 및 문서 업데이트

parent 00587836
# 1. 인프라 (Infrastructure)
이 디렉터리는 서버, 네트워크, 배포 등 금도의 인프라와 관련된 모든 설정과 문서를 포함합니다.
## 📁 하위 디렉터리
- **[docker/](./docker/)**: Docker 관련 설정, Docker Compose 파일 및 관련 스크립트를 포함합니다.
- **[nginx/](./nginx/)**: Nginx 웹 서버의 설정 파일 및 관련 문서를 포함합니다.
- **[server_backup/](./server_backup/)**: 서버 데이터 백업을 위한 스크립트 및 가이드를 포함합니다.
각 디렉터리로 이동하여 필요한 설정과 문서를 찾아보세요.
\ No newline at end of file
# AI 분야 취업 포트폴리오 가이드라인
## 개요
이 가이드라인은 AI 전문가 양성 과정 학생들이 산업 현장에서 경쟁력 있는 취업 포트폴리오를 구성하는데 도움을 주기 위해 작성되었습니다. 실제 기업 채용 담당자들의 피드백을 바탕으로, AI 엔지니어, 데이터 사이언티스트, ML 연구원 등 다양한 직무에 맞는 포트폴리오 구성 방법을 안내합니다.
## 목차
1. [포트폴리오의 중요성과 목적](#1-포트폴리오의-중요성과-목적)
2. [직무별 포트폴리오 차별화 전략](#2-직무별-포트폴리오-차별화-전략)
3. [포트폴리오 구성 요소](#3-포트폴리오-구성-요소)
4. [프로젝트 기획 및 선정](#4-프로젝트-기획-및-선정)
5. [기술적 깊이 입증하기](#5-기술적-깊이-입증하기)
6. [실무 역량 강조하기](#6-실무-역량-강조하기)
7. [효과적인 포트폴리오 발표 방법](#7-효과적인-포트폴리오-발표-방법)
8. [포트폴리오 체크리스트](#8-포트폴리오-체크리스트)
## 1. 포트폴리오의 중요성과 목적
### 왜 포트폴리오가 중요한가?
AI 분야는 단순한 이론적 지식보다 **실질적인 문제 해결 능력****코드 구현 역량**이 중요합니다. 포트폴리오는 이러한 능력을 채용 담당자에게 직접적으로 보여줄 수 있는 강력한 증거입니다.
### 포트폴리오의 주요 목적
1. **기술적 역량 증명**: 실제 코드와 결과물을 통한 기술력 입증
2. **문제 해결 과정 공유**: 도전 과제를 어떻게 해결했는지 보여주는 사고 과정 전시
3. **지속적 학습 의지 표현**: 최신 기술 트렌드를 따라가는 학습 의지 증명
4. **실무 적응력 제시**: 실제 업무 환경과 유사한 프로젝트 경험 공유
### 채용 담당자가 포트폴리오에서 보고 싶은 것
- **실용적 접근법**: 학문적 접근보다 실질적인 비즈니스 문제 해결 능력
- **코드 품질**: 깔끔하고 유지보수 가능한 코드 작성 능력
- **의사소통 능력**: 복잡한 기술적 개념을 명확하게 설명하는 능력
- **프로젝트 완성도**: 시작부터 끝까지 프로젝트를 완료하는 끈기와 실행력
## 2. 직무별 포트폴리오 차별화 전략
### AI/ML 엔지니어
- **강조점**: 모델 구현, 파이프라인 구축, 시스템 통합, 성능 최적화
- **추천 프로젝트**:
- 엔드투엔드 ML 파이프라인 구축
- 모델 서빙 API 개발
- 분산 학습 시스템 구현
- MLOps 도구 활용 사례
- **필수 기술**: Docker, Kubernetes, CI/CD, 클라우드 서비스, REST API
### 데이터 사이언티스트
- **강조점**: 데이터 분석, 통계적 모델링, 비즈니스 인사이트 도출
- **추천 프로젝트**:
- 탐색적 데이터 분석(EDA) 및 시각화
- A/B 테스트 설계 및 분석
- 예측 모델링 및 비즈니스 영향 평가
- 데이터 기반 의사결정 사례
- **필수 기술**: 통계 분석, 데이터 시각화, 가설 검정, SQL, Pandas/NumPy
### 머신러닝 연구원
- **강조점**: 알고리즘 이해, 논문 구현, 새로운 방법론 개발
- **추천 프로젝트**:
- 최신 논문 구현 및 개선
- 알고리즘 벤치마킹 및 성능 비교
- 새로운 모델 아키텍처 제안
- 실험 설계 및 결과 분석
- **필수 기술**: 수학적 모델링, 딥러닝 프레임워크, 논문 리서치, 실험 설계
### 컴퓨터 비전 전문가
- **강조점**: 이미지/비디오 처리, 객체 인식, 이미지 생성
- **추천 프로젝트**:
- 객체 검출 및 세그먼테이션
- 이미지 분류 및 검색 시스템
- 얼굴 인식 또는 감정 분석
- 생성형 모델(GAN, Diffusion) 응용
- **필수 기술**: OpenCV, 이미지 전처리, CNN 아키텍처, 컴퓨터 비전 라이브러리
### 자연어처리(NLP) 전문가
- **강조점**: 텍스트 처리, 언어 모델링, 정보 추출
- **추천 프로젝트**:
- 감성 분석 또는 텍스트 분류
- 챗봇 또는 대화 시스템
- 정보 추출 및 요약
- 대규모 언어 모델(LLM) 미세 조정
- **필수 기술**: 텍스트 전처리, 임베딩 기법, Transformer 아키텍처, 토크나이저
## 3. 포트폴리오 구성 요소
### 필수 포함 요소
1. **개인 소개**: 자신의 전문 분야, 관심사, 커리어 목표 (1-2 문단)
2. **기술 스택**: 프로그래밍 언어, 프레임워크, 도구 등 (시각적 요소 활용)
3. **주요 프로젝트** (3-5개):
- 프로젝트명과 한 줄 요약
- 해결하고자 한 문제
- 사용한 기술과 방법론
- 결과 및 성과
- 배운 점과 개선 사항
- GitHub 링크
4. **교육 및 자격증**: 관련 학위, 코스, 자격증 (간결하게)
5. **연락처 및 소셜 링크**: GitHub, LinkedIn, 개인 블로그/웹사이트
### 포트폴리오 형식 옵션
1. **개인 웹사이트**: 가장 전문적이고 커스터마이징 자유도 높음
2. **GitHub 프로필**: 코드 중심 포트폴리오, README.md 활용
3. **노션(Notion)**: 문서와 멀티미디어 통합 용이, 공유 간편
4. **PDF 문서**: 전통적 형식, 이메일 첨부 및 출력 용이
5. **인터랙티브 노트북**: Jupyter Notebook 또는 Colab, 코드와 결과 시각화
## 4. 프로젝트 기획 및 선정
### 좋은 프로젝트의 기준
- **실제 문제 해결**: 실생활 또는 비즈니스 문제 해결에 중점
- **기술적 깊이**: 단순 튜토리얼 이상의 깊이 있는 구현
- **완성도**: 시작부터 끝까지 완료된 프로젝트
- **독창성**: 남들과 차별화되는 아이디어나 접근법
- **확장성**: 추가 기능이나 개선 가능성 제시
### 추천 프로젝트 유형
1. **산업 문제 해결형**: 특정 산업의 실제 문제 해결 (예: 의료 영상 진단, 금융 사기 탐지)
2. **서비스 개발형**: 실제 사용 가능한 서비스 구현 (예: 추천 시스템, 챗봇)
3. **데이터 분석형**: 흥미로운 데이터셋에 대한 심층 분석 (예: 소비자 행동 분석, 트렌드 예측)
4. **알고리즘 개선형**: 기존 알고리즘 개선 또는 새로운 접근법 제안
5. **복제 및 확장형**: 유명 논문/서비스 복제 후 자체 아이디어로 확장
### 피해야 할 프로젝트
- 너무 단순한 분류 문제 (예: MNIST, Iris 데이터셋만 사용)
- 튜토리얼을 그대로 따라한 프로젝트
- 데이터 수집 및 전처리 과정이 없는 프로젝트
- 목표와 평가 지표가 불분명한 프로젝트
- 비즈니스/실용적 가치를 설명하지 못하는 프로젝트
## 5. 기술적 깊이 입증하기
### 코드 품질 향상 방법
- **클린 코드 원칙**: 읽기 쉽고 유지보수 가능한 코드 작성
- **모듈화**: 재사용 가능한 컴포넌트로 구조화
- **문서화**: 함수, 클래스, 모듈에 대한 명확한 주석 및 문서
- **테스트**: 단위 테스트, 통합 테스트 구현
- **버전 관리**: 의미 있는 커밋 메시지와 브랜치 전략
### 기술적 의사결정 설명하기
- 선택한 알고리즘/모델의 이유
- 대안 기술과의 비교 및 트레이드오프
- 성능 최적화를 위한 접근법
- 직면한 기술적 도전과 해결 방법
- 확장성 및 유지보수성 고려사항
### 실험 및 검증 방법
- 명확한 평가 지표 설정
- 교차 검증 및 통계적 유의성 테스트
- 베이스라인 모델과의 비교
- 하이퍼파라미터 최적화 과정
- 모델 해석 및 설명 가능성
## 6. 실무 역량 강조하기
### 프로젝트 관리 능력 보여주기
- GitHub Issues, Project 보드 활용
- 명확한 프로젝트 목표 및 마일스톤 설정
- 일정 관리 및 작업 우선순위 설정
- 문제 해결 과정의 체계적 기록
### 협업 및 의사소통 능력
- 오픈소스 프로젝트 기여 내역
- 팀 프로젝트에서의 역할과 기여도
- 코드 리뷰 경험 및 피드백 수용 사례
- 기술적 내용의 명확한 문서화 및 설명
### 산업 표준 및 도구 활용
- 개발환경: Docker, Git, IDE 설정
- CI/CD: GitHub Actions, Jenkins
- 모니터링: TensorBoard, MLflow, Weights & Biases
- 배포: 클라우드 서비스(AWS, GCP, Azure)
- 협업 도구: Jira, Confluence, Slack
## 7. 효과적인 포트폴리오 발표 방법
### 발표 자료 준비
- 명확한 문제 정의로 시작
- 핵심 기술적 도전과 해결책 강조
- 결과를 시각적으로 효과적으로 표현
- 실제 데모 또는 시연 영상 준비
- 간결하고 전문적인 슬라이드 디자인
### 기술 인터뷰 대비
- 프로젝트 관련 심층 질문 예상 및 준비
- 기술적 결정의 이유와 대안 설명 가능
- 사용한 알고리즘/모델의 원리 설명 준비
- 발생 가능한 엣지 케이스와 한계점 인지
- 확장 및 개선 방향에 대한 아이디어 준비
### 비전공자를 위한 설명 전략
- 전문 용어를 줄이고 일상 언어로 설명
- 비유와 시각적 요소 활용
- 비즈니스 가치와 실용적 측면 강조
- 중요 개념은 간결하게 정의 제공
- 스토리텔링 방식으로 흥미 유발
## 8. 포트폴리오 체크리스트
### 내용적 측면
- [ ] 3-5개의 다양한 깊이/복잡도를 가진 프로젝트 포함
- [ ] 각 프로젝트가 특정 기술/역량을 보여줌
- [ ] 문제 정의, 접근법, 결과, 배운 점이 명확히 설명됨
- [ ] 코드와 프로젝트가 적절히 문서화됨
- [ ] 실제 사용자/비즈니스 가치를 설명함
### 기술적 측면
- [ ] GitHub 저장소가 잘 정리되어 있음 (README, .gitignore 등)
- [ ] 코드 스타일과 구조가 일관적이고 깔끔함
- [ ] 적절한 테스트와 검증 과정이 포함됨
- [ ] 데이터 처리 및 모델링 파이프라인이 명확함
- [ ] 결과의 시각화와 해석이 포함됨
### 형식적 측면
- [ ] 오타나 문법 오류가 없음
- [ ] 일관된 시각적 스타일과 구조
- [ ] 간결하고 전문적인 어조
- [ ] 쉽게 탐색 가능한 구조
- [ ] 모든 링크와 참조가 작동함
## 결론
효과적인 AI 분야 취업 포트폴리오는 단순한 프로젝트 나열이 아니라, 지원자의 기술적 역량과 문제 해결 능력, 그리고 실무 적응력을 종합적으로 보여주는 도구입니다. 이 가이드라인을 참고하여 자신만의 강점을 효과적으로 드러내는 포트폴리오를 구성하시기 바랍니다.
무엇보다 포트폴리오는 자신의 열정과 지속적인 학습 의지를 보여주는 살아있는 문서입니다. 정기적으로 업데이트하고 발전시켜 나가는 것이 중요합니다.
\ No newline at end of file
# 산업별 AI 적용 사례 연구
## 개요
이 문서는 다양한 산업 분야에서 AI가 어떻게 적용되고 있는지에 대한 사례 연구를 제공합니다. 각 산업별로 대표적인 AI 활용 사례, 구현 방법, 사용된 기술, 비즈니스 성과 등을 포함하고 있으며, 학생들이 실제 산업 환경에서의 AI 적용에 대한 이해를 높이고 자신의 프로젝트에 활용할 수 있도록 구성되었습니다.
## 목차
1. [헬스케어 산업](#1-헬스케어-산업)
2. [금융 산업](#2-금융-산업)
3. [제조 산업](#3-제조-산업)
4. [유통/소매 산업](#4-유통소매-산업)
5. [농업 산업](#5-농업-산업)
## 1. 헬스케어 산업
### 1.1 의료 영상 진단 - 서울아산병원 & VUNO
#### 배경 및 과제
서울아산병원은 방대한 양의 X-ray, MRI, CT 영상 데이터를 처리하고 진단 정확도를 높이는 것이 주요 과제였습니다. 특히 방사선 전문의의 업무 부담이 높아 진단 시간 단축이 필요한 상황이었습니다.
#### AI 적용 솔루션
- **기술:** 딥러닝 기반 의료 영상 분석 (VUNO Med-Chest X-ray)
- **주요 알고리즘:**
- CNN (Convolutional Neural Networks) 기반 이미지 분석
- 앙상블 학습을 통한 정확도 향상
- GradCAM을 활용한 판독 근거 시각화
#### 구현 방법
1. 10만 건 이상의 흉부 X-ray 데이터 학습
2. 다양한 폐 질환에 대한 다중 레이블 분류 모델 구축
3. 병원 PACS(의료영상저장전송시스템)과 통합
#### 성과 및 결과
- 폐결절, 기흉 등 주요 폐질환 탐지 정확도 95% 이상
- 진단 시간 평균 30% 단축
- 방사선과 의사의 업무 효율성 20% 향상
- 특히 야간/응급 상황에서 신속한 의사결정 지원
### 1.2 개인 맞춤형 치료 - 삼성서울병원 정밀의료 프로젝트
#### 배경 및 과제
암 환자들은 같은 유형의 암이라도 유전적 특성과 환경에 따라 치료 반응이 다양합니다. 삼성서울병원은 이러한 차이를 고려한 맞춤형 치료법 개발이 필요했습니다.
#### AI 적용 솔루션
- **기술:** 유전체 데이터 분석 및 AI 기반 치료법 예측
- **주요 알고리즘:**
- 유전체 시퀀싱 데이터 분석을 위한 딥러닝
- 약물 반응 예측을 위한 그래프 신경망(GNN)
- 환자 군집화를 위한 비지도 학습
#### 구현 방법
1. 1만 명 이상의 한국인 암 환자 유전체 및 임상 데이터 수집
2. 유전자 변이와 약물 반응 간의 상관관계 분석
3. 환자별 최적 치료 옵션 예측 모델 개발
#### 성과 및 결과
- 위암 환자 항암제 반응 예측 정확도 78%
- 불필요한 약물 처방 20% 감소
- 치료 성공률 15% 향상
- 환자별 맞춤형 항암치료 프로토콜 수립
## 2. 금융 산업
### 2.1 이상 금융거래 탐지 - 신한은행 FDS(Fraud Detection System)
#### 배경 및 과제
금융 사기 기법이 갈수록 정교해지면서 기존의 규칙 기반 탐지 시스템으로는 새로운 유형의 사기를 탐지하기 어려워졌습니다. 신한은행은 이를 개선하기 위해 AI 기반 시스템을 도입했습니다.
#### AI 적용 솔루션
- **기술:** 실시간 이상 거래 탐지 시스템
- **주요 알고리즘:**
- LSTM(Long Short-Term Memory) 네트워크
- 오토인코더(Autoencoder) 기반 이상 탐지
- XGBoost를 활용한 특성 중요도 분석
#### 구현 방법
1. 3년간의 거래 데이터와 사기 사례 5만 건 학습
2. 고객별 거래 패턴 프로파일링
3. 실시간 거래 모니터링 및 점수화 시스템 구축
#### 성과 및 결과
- 사기 거래 탐지율 76%에서 92%로 향상
- 오탐(false positive) 비율 40% 감소
- 연간 2,500억원 규모의 사기 피해 방지
- 고객 신뢰도 증가 및 브랜드 가치 향상
### 2.2 로보어드바이저 - NH투자증권 QV로보어드바이저
#### 배경 및 과제
개인 투자자들은 전문적인 자산 관리 서비스를 높은 비용으로만 이용할 수 있었습니다. NH투자증권은 AI를 활용해 저비용으로 맞춤형 자산관리 서비스를 제공하고자 했습니다.
#### AI 적용 솔루션
- **기술:** 머신러닝 기반 자산 배분 및 투자 추천 엔진
- **주요 알고리즘:**
- 강화학습을 통한 자산 배분 최적화
- 시계열 예측 모델(ARIMA, Prophet)
- 클러스터링을 통한 투자자 유형 분류
#### 구현 방법
1. 30년간의 글로벌 자산 가격 데이터 학습
2. 투자자 위험 성향 분석 알고리즘 개발
3. 최적 포트폴리오 구성 및 정기 리밸런싱 자동화
#### 성과 및 결과
- 서비스 출시 2년 내 10만 명 이상의 고객 확보
- 평균 운용 수익률 벤치마크 대비 2.5% 초과 달성
- 자산관리 서비스 민주화 실현
- 자산관리 수수료 70% 절감 효과
## 3. 제조 산업
### 3.1 스마트 팩토리 - 포스코 인공지능 용광로
#### 배경 및 과제
제철 공정에서 용광로는 높은 에너지를 소비하며, 품질 유지를 위해 전문가의 지속적인 모니터링이 필요했습니다. 포스코는 AI를 도입해 공정을 최적화하고 에너지 효율을 높이고자 했습니다.
#### AI 적용 솔루션
- **기술:** AI 기반 용광로 자동 제어 시스템
- **주요 알고리즘:**
- 디지털 트윈 기반 시뮬레이션
- 강화학습을 통한 최적 제어 전략
- 다변량 시계열 분석
#### 구현 방법
1. 용광로 센서 네트워크 구축 (온도, 압력, 가스 조성 등)
2. 5년간의 운영 데이터를 활용한 AI 모델 학습
3. 실시간 모니터링 및 자동 제어 시스템 통합
#### 성과 및 결과
- 에너지 소비 10% 절감
- 철강 품질 일관성 15% 향상
- 연간 2,000억원의 비용 절감
- 탄소 배출량 8% 감소
### 3.2 예지 정비 - 현대자동차 스마트 공장
#### 배경 및 과제
자동차 생산 라인의 장비 고장은 생산 중단과 막대한 경제적 손실로 이어집니다. 현대자동차는 예방적 정비에서 AI 기반 예지 정비로 전환하고자 했습니다.
#### AI 적용 솔루션
- **기술:** IoT 센서 및 머신러닝 기반 예지 정비 시스템
- **주요 알고리즘:**
- RNN 기반 시계열 이상 탐지
- 생존 분석(Survival Analysis)을 통한 잔여 수명 예측
- 다중 센서 융합 분석
#### 구현 방법
1. 생산 설비에 IoT 센서 설치 (진동, 소음, 온도 등)
2. 설비별 정상 작동 패턴 학습 및 이상 징후 감지 모델 개발
3. 유지보수 일정 최적화 시스템 구축
#### 성과 및 결과
- 계획되지 않은 설비 중단 시간 67% 감소
- 유지보수 비용 35% 절감
- 설비 수명 20% 연장
- 생산성 8% 향상
## 4. 유통/소매 산업
### 4.1 개인화된 추천 시스템 - 쿠팡 제품 추천 엔진
#### 배경 및 과제
온라인 쇼핑몰에서 고객이 관심을 가질 만한 제품을 효과적으로 추천하는 것은 매출 증대의 핵심 요소입니다. 쿠팡은 방대한 제품군과 고객층에 맞는 고도화된 추천 시스템이 필요했습니다.
#### AI 적용 솔루션
- **기술:** 딥러닝 기반 하이브리드 추천 시스템
- **주요 알고리즘:**
- 협업 필터링(Collaborative Filtering)
- BERT 기반 상품 설명 텍스트 분석
- 세션 기반 추천을 위한 GNN(Graph Neural Networks)
#### 구현 방법
1. 구매 이력, 검색 기록, 제품 리뷰 등 다양한 데이터 소스 통합
2. 제품간 연관성 및 사용자-제품 상호작용 그래프 구축
3. 실시간 개인화된 추천 API 개발 및 A/B 테스트
#### 성과 및 결과
- 추천 클릭률(CTR) 32% 증가
- 장바구니 추가율 25% 상승
- 교차 판매(Cross-selling) 45% 증가
- 개인화된 이메일 마케팅 효과 60% 향상
### 4.2 수요 예측 및 재고 관리 - 롯데마트 SCM 최적화
#### 배경 및 과제
롯데마트는 다양한 제품군에 대한 정확한 수요 예측과 효율적인 재고 관리가 필요했습니다. 특히 신선식품의 경우 과잉 재고는 폐기 비용을, 재고 부족은 기회 손실을 초래합니다.
#### AI 적용 솔루션
- **기술:** 머신러닝 기반 수요 예측 및 재고 최적화
- **주요 알고리즘:**
- LightGBM을 활용한 수요 예측
- 베이지안 최적화를 통한 재고 수준 결정
- 시공간적 패턴 분석을 위한 CNN-LSTM 하이브리드 모델
#### 구현 방법
1. 5년간의 판매 데이터, 프로모션 기록, 날씨 데이터 등 통합
2. 지역별, 상품별 수요 패턴 모델링
3. 자동화된 발주 제안 시스템 구축
#### 성과 및 결과
- 신선식품 폐기율 35% 감소
- 재고 부족으로 인한 기회 손실 25% 감소
- 재고 유지 비용 18% 절감
- 전체 공급망 비용 12% 절감
## 5. 농업 산업
### 5.1 정밀 농업 - 농촌진흥청 스마트팜
#### 배경 및 과제
한국의 농업은 고령화와 인력 부족으로 어려움을 겪고 있습니다. 농촌진흥청은 AI와 IoT를 활용한 정밀 농업을 통해 생산성을 높이고 인력 의존도를 줄이고자 했습니다.
#### AI 적용 솔루션
- **기술:** AI 기반 작물 모니터링 및 환경 제어 시스템
- **주요 알고리즘:**
- 컴퓨터 비전을 통한 작물 상태 분석
- 강화학습 기반 최적 환경 제어
- 시계열 예측을 통한 수확량 예측
#### 구현 방법
1. 온실 내 센서 네트워크 구축 (온도, 습도, 조도, CO2 등)
2. 드론 이미지와 근접 센서를 활용한 작물 상태 모니터링
3. 통합 제어 시스템을 통한 자동화된 환경 관리
#### 성과 및 결과
- 토마토 재배 수확량 23% 증가
- 물 사용량 30% 절감
- 농약 및 비료 사용 25% 감소
- 인력 투입 시간 40% 절감
### 5.2 병충해 조기 감지 - 경상북도 사과농가 AI 방제 시스템
#### 배경 및 과제
사과 농가는 매년 병충해로 인한 막대한 손실을 입고 있으며, 기존의 정기적 방제는 비효율적이고 환경에도 부담을 주었습니다. 경상북도는 AI를 활용한 정밀 방제 시스템을 개발하고자 했습니다.
#### AI 적용 솔루션
- **기술:** 이미지 인식 기반 병충해 조기 감지 시스템
- **주요 알고리즘:**
- 객체 탐지를 위한 YOLOv5 기반 모델
- 전이학습을 통한 적은 데이터로 높은 성능 달성
- 환경 센서 데이터와 영상 정보의 다중 모달 분석
#### 구현 방법
1. 드론 및 고정 카메라를 통한 정기적 이미지 수집
2. 주요 병충해 10종에 대한 학습 데이터셋 구축
3. 위험도 분석 및 정밀 방제 권고 시스템 개발
#### 성과 및 결과
- 병충해 조기 발견율 65% 향상
- 농약 사용량 40% 감소
- 수확량 손실률 50% 감소
- 친환경 사과 인증 비율 증가
## 결론
이상의 사례 연구들은 AI가 다양한 산업 분야에서 실질적인 문제를 해결하고 비즈니스 가치를 창출하는 방식을 보여줍니다. 이러한 사례들은 학생들이 자신의 분야에서 AI를 적용할 수 있는 영감과 실질적인 지침을 제공할 수 있을 것입니다. 향후 더 많은 사례 연구가 추가될 예정이며, 각 산업별로 더 심층적인 기술 구현 방법과 데이터 처리 방식에 대한 내용도 확충될 것입니다.
\ No newline at end of file
# AWS/GCP/Azure 무료 티어 활용 AI 실습 환경 구축 가이드
## 개요
이 문서는 주요 클라우드 서비스인 AWS, Google Cloud Platform(GCP), Microsoft Azure의 무료 티어 서비스를 활용하여 AI 학습 및 실습 환경을 구축하는 방법을 안내합니다. 각 클라우드 서비스별로 무료로 사용할 수 있는 리소스와 설정 방법, 그리고 주의사항을 포함하고 있습니다.
## 목차
1. [클라우드 서비스 선택 가이드](#1-클라우드-서비스-선택-가이드)
2. [AWS 무료 티어 활용 가이드](#2-aws-무료-티어-활용-가이드)
3. [GCP 무료 티어 활용 가이드](#3-gcp-무료-티어-활용-가이드)
4. [Azure 무료 티어 활용 가이드](#4-azure-무료-티어-활용-가이드)
5. [공통 Docker 배포 방법](#5-공통-docker-배포-방법)
6. [비용 관리 및 모니터링](#6-비용-관리-및-모니터링)
## 1. 클라우드 서비스 선택 가이드
| 고려사항 | AWS | GCP | Azure |
|---------|-----|-----|-------|
| **초기 크레딧** | 1년간 무료 티어 | $300 크레딧 (90일) | $200 크레딧 (30일) |
| **ML 특화 서비스** | SageMaker | Vertex AI | Azure ML |
| **GPU 가용성** | 제한적 (무료 티어에 없음) | Colab 무료 (제한적) | 제한적 (무료 티어에 없음) |
| **사용 난이도** | 복잡함 | 중간 | 사용자 친화적 |
| **문서화 수준** | 매우 상세함 | 상세함 | 매우 상세함 |
| **교육용 적합성** | 높음 (AWS Academy) | 높음 (Qwiklabs) | 높음 (MS Learn) |
### 추천 조합
- **초보자**: Azure 또는 GCP
- **중급자**: 모든 서비스 적합
- **고급자**: AWS (특히 MLOps 관점)
- **무료 GPU 필요시**: GCP (Colab)
## 2. AWS 무료 티어 활용 가이드
### AWS 무료 티어 개요
AWS 무료 티어는 12개월 무료, 항상 무료, 단기 무료 평가판의 세 가지 유형으로 제공됩니다.
### AI/ML 학습에 유용한 AWS 무료 서비스
1. **EC2** (12개월 무료)
- t2.micro 또는 t3.micro 인스턴스 750시간/월
- Python, Jupyter, TensorFlow 등 설치 가능
2. **S3** (12개월 무료)
- 5GB 스토리지, 데이터셋 저장에 활용
3. **Amazon SageMaker** (항상 무료)
- SageMaker Studio Lab - 무료 GPU 제한적 제공
4. **AWS Lambda** (항상 무료)
- 매월 100만 건의 요청, 간단한 ML 추론 API 구현 가능
### AWS 환경 설정 가이드
1. **계정 생성**
```bash
# AWS 계정 생성: https://aws.amazon.com
# 신용카드 등록 필요 (청구되지 않음)
```
2. **EC2 인스턴스 시작**
```bash
# AWS 콘솔 > EC2 > 인스턴스 시작
# Amazon Linux 2 선택
# t2.micro 또는 t3.micro 선택 (무료 티어 적격)
```
3. **Docker로 AI 개발환경 설정**
```bash
# EC2 접속 후
sudo yum update -y
sudo amazon-linux-extras install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker ec2-user
# 재로그인 후
# 도커 컨테이너 실행 (JupyterLab)
docker run -d -p 8888:8888 -v $PWD:/home/jovyan/work jupyter/datascience-notebook
```
### 무료 티어 한도 모니터링
- AWS Billing 대시보드 활용
- 비용 알림 설정: CloudWatch > 결제 알림
## 3. GCP 무료 티어 활용 가이드
### GCP 무료 티어 개요
GCP는 $300 무료 크레딧 (90일)과 일부 항상 무료 제품을 제공합니다.
### AI/ML 학습에 유용한 GCP 무료 서비스
1. **Google Colab** (별도 계정)
- 무료 GPU/TPU 액세스 (세션 시간 제한)
- 노트북 형태로 개발 가능
2. **Compute Engine** (무료 크레딧 사용)
- e2-micro 인스턴스 (vCPU 2개, 1GB 메모리)
3. **Cloud Storage** (항상 무료)
- 5GB 스토리지
4. **BigQuery** (항상 무료)
- 매월 1TB 쿼리 처리, 데이터 분석 학습에 유용
### GCP 환경 설정 가이드
1. **계정 생성**
```bash
# GCP 계정 생성: https://cloud.google.com
# 신용카드 등록 필요 (청구되지 않음)
```
2. **Compute Engine VM 인스턴스 생성**
```bash
# GCP 콘솔 > Compute Engine > VM 인스턴스
# e2-micro 선택
# Ubuntu 20.04 LTS 선택
```
3. **Docker로 AI 개발환경 설정**
```bash
# VM 접속 후
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker $USER
# 재로그인 후
# 도커 컨테이너 실행 (JupyterLab)
docker run -d -p 8888:8888 -v $PWD:/home/jovyan/work jupyter/datascience-notebook
```
### Google Colab 활용
1. https://colab.research.google.com 접속
2. 새 노트북 생성
3. 런타임 > 런타임 유형 변경 > GPU 또는 TPU 선택
## 4. Azure 무료 티어 활용 가이드
### Azure 무료 티어 개요
Azure는 $200 무료 크레딧 (30일)과 40개 이상의 항상 무료 서비스를 제공합니다.
### AI/ML 학습에 유용한 Azure 무료 서비스
1. **Azure Virtual Machines** (무료 크레딧 사용)
- B1s 인스턴스 (1 vCPU, 1GB 메모리)
2. **Azure Storage** (항상 무료)
- 5GB 스토리지
3. **Azure Machine Learning** (무료 크레딧 사용)
- 워크스페이스 생성 및 간단한 모델 학습
4. **Azure Cognitive Services** (항상 무료)
- 일정량 내 API 호출 무료
### Azure 환경 설정 가이드
1. **계정 생성**
```bash
# Azure 계정 생성: https://azure.microsoft.com
# 신용카드 등록 필요 (청구되지 않음)
```
2. **가상머신 생성**
```bash
# Azure 포털 > 가상 머신 > 추가
# B1s 또는 B1ls 선택
# Ubuntu 20.04 LTS 선택
```
3. **Docker로 AI 개발환경 설정**
```bash
# VM 접속 후
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker $USER
# 재로그인 후
# 도커 컨테이너 실행 (JupyterLab)
docker run -d -p 8888:8888 -v $PWD:/home/jovyan/work jupyter/datascience-notebook
```
## 5. 공통 Docker 배포 방법
AI 강의 도커 환경을 각 클라우드 서비스에 일관되게 배포하기 위한 방법입니다.
### 준비 사항
1. 클라우드 인스턴스 접속 권한
2. Docker 설치 완료
3. `docker-compose.yml` 파일 준비
### 배포 단계
1. **프로젝트 클론**
```bash
git clone https://github.com/your-org/ai-course.git
cd ai-course
```
2. **도커 컴포즈 실행**
```bash
# CPU 버전 (무료 티어에 적합)
docker-compose -f docker-compose.yml up -d
# GPU 버전 (유료 인스턴스에서만)
docker-compose -f docker-compose.gpu.yml up -d
```
3. **포트포워딩 설정**
- 클라우드 콘솔에서 인바운드 포트 허용 (8888, 8000, 5000)
4. **접속 방법**
- 브라우저에서 `http://<클라우드_인스턴스_IP>:8888`
- 토큰: `ai_course_token`
## 6. 비용 관리 및 모니터링
### AWS 비용 관리
- AWS Budgets 설정
- AWS Cost Explorer 활용
- 미사용 리소스 정리 (EBS 볼륨, 탄력적 IP 등)
### GCP 비용 관리
- 결제 알림 설정
- 결제 내보내기 설정
- 할당량 한도 설정
### Azure 비용 관리
- Azure Cost Management 활용
- 예산 알림 설정
- 미사용 리소스 자동 종료 설정
## 결론
각 클라우드 서비스의 무료 티어는 AI 학습을 시작하기에 충분한 자원을 제공합니다. 실습 목적에 가장 맞는 서비스를 선택하고, 비용 모니터링을 통해 예상치 못한 청구가 발생하지 않도록 주의하세요. 더 높은 성능이 필요한 딥러닝 작업의 경우 GCP Colab이나 유료 서비스를 고려하는 것이 좋습니다.
\ No newline at end of file
gcode/
stl/
venv/
log/
.env
fff_option.txt
# 3D 프린터 슬라이싱 API
이 프로젝트는 [Flask](https://flask.palletsprojects.com/)를 사용하여 구축된 웹 API로, [PrusaSlicer](https://www.prusa3d.com/page/prusaslicer_424/)를 이용해 STL 3D 모델 파일을 G-code로 변환하는 기능을 제공합니다. Prusa MK4 3D 프린터에 최적화된 설정을 포함하고 있습니다.
## 주요 기능
- **STL to G-code 변환**: `/slice` 엔드포인트를 통해 STL 파일을 G-code로 슬라이싱합니다.
- **상세한 파라미터 설정**: PrusaSlicer에서 제공하는 거의 모든 슬라이싱 파라미터를 API 요청 시점에 커스터마이징할 수 있습니다.
- **오류 처리**: 슬라이싱 과정에서 발생할 수 있는 일반적인 오류들을 감지하고, 구체적인 오류 코드와 메시지를 반환합니다.
- **파일 관리 및 프록시**: 생성된 G-code 파일을 다운로드하고, Prusa 프린터의 웹 인터페이스인 PrusaLink로 요청을 전달하는 프록시 기능을 제공합니다.
## API 엔드포인트
### `POST /slice`
STL 파일을 업로드하고 슬라이싱 옵션을 지정하여 G-code를 생성합니다.
- **Request Body (`multipart/form-data`):**
- `file` (file, optional): 슬라이싱할 `.stl` 파일. 서버에 파일이 이미 존재하는 경우 `orignlFileNm`으로 대체할 수 있습니다.
- `orignlFileNm` (string, optional): 서버에 미리 업로드된 파일의 이름.
- `...options`: PrusaSlicer에 전달할 키-값 형태의 슬라이싱 옵션들. (자세한 내용은 아래의 '슬라이싱 옵션' 섹션 참조)
- **Success Response (200 OK):**
```json
{
"success": true,
"message": "모델이 성공적으로 슬라이스되었습니다."
}
```
- **Error Response:**
- **파일 관련 오류:**
- `100`: 업로드된 파일이 없음.
- `200`: `orignlFileNm`으로 지정된 파일이 서버 경로에 없음.
- `300`: STL 파일이 아닌 다른 확장자 파일 업로드.
- **슬라이싱 오류 (PrusaSlicer):**
- `401`: 첫 레이어에 압출이 없는 객체가 존재.
- `402`: 객체에 대한 압출이 생성되지 않음.
- `403`: 현재 설정으로 모델을 인쇄할 수 없음.
- `404`: 지지대(support) 없이 인쇄할 수 없는 객체가 있음.
- `405`: 레이어가 감지되지 않음 (STL 파일 복구 필요).
- `406`: 현재 설정으로 패드(pad)를 생성할 수 없음.
- `407`: 인쇄할 수 없는 객체가 있음 (지지대 설정 조정 필요).
- `400`: 그 외의 알 수 없는 슬라이싱 오류.
### `GET /getGCodeFile`
서버에 생성된 G-code 파일을 다운로드합니다.
- **Query Parameters:**
- `fileName` (string, required): 다운로드할 G-code 파일의 이름.
- **Success Response (200 OK):**
- `Content-Type: application/octet-stream`
- G-code 파일 데이터 스트림.
### `GET /getPrusaConfig`
서버에 저장된 PrusaSlicer 설정 파일(`fff_option.txt`)의 내용을 반환합니다.
### `ANY /proxy`
PrusaLink 장비로 API 요청을 전달하는 프록시 역할을 합니다. PrusaLink API의 엔드포인트와 파라미터를 그대로 사용합니다.
## 설치 및 실행
### 요구사항
- Python 3.x
- PrusaSlicer
- Flask 및 기타 Python 패키지
### 설정
1. **소스 코드 클론:**
```bash
git clone <repository-url>
cd <repository-directory>
```
2. **환경 변수 설정:**
프로젝트 루트에 `.env` 파일을 생성하고 아래 내용을 채웁니다. 이 프로젝트는 하드코딩된 경로를 사용하므로, 환경에 맞게 소스 코드(`app.py`) 수정이 필요할 수 있습니다.
```
# .env
PRUSA_LINK_URL=http://<your-prusa-link-ip>
PRUSA_LINK_API_KEY=<your-prusa-link-api-key>
```
3. **Python 의존성 설치:**
가상 환경을 생성하고 필요한 패키지를 설치합니다.
```bash
python -m venv venv
source venv/bin/activate
pip install Flask Flask-Cors python-dotenv requests
```
(프로젝트에 `requirements.txt` 파일을 생성하여 관리하는 것을 권장합니다.)
4. **PrusaSlicer 설치:**
PrusaSlicer를 시스템에 설치해야 합니다. `app.py`에서는 실행 경로가 `/usr/bin/PrusaSlicer`로 하드코딩되어 있으므로, 실제 설치 경로가 다를 경우 코드를 수정해야 합니다.
5. **애플리케이션 실행:**
```bash
flask run
```
## 슬라이싱 옵션
API를 통해 PrusaSlicer의 다양한 설정을 제어할 수 있습니다. `slicer_test.py` 파일에 거의 모든 사용 가능한 옵션이 정의되어 있습니다.
<details>
<summary><b>전체 옵션 목록 보기 (예시)</b></summary>
```python
options = {
'layer-height': '0.1',
'first-layer-height': '0.2',
'perimeters': '3',
'top-solid-layers': '8',
'bottom-solid-layers': '6',
'fill-density': '15%',
'fill-pattern': 'grid',
'support-material': '',
'support-material-auto': '',
# ... and many more options
}
```
</details>
## 커스텀 G-code
이 API는 Prusa MK4 프린터에 최적화된 시작(`start_gcode`) 및 종료(`end_gcode`) G-code를 슬라이싱 결과물에 자동으로 삽입합니다. 이 코드는 `app.py` 내에 변수로 정의되어 있으며, 필요에 따라 수정할 수 있습니다.
\ No newline at end of file
import logging
from flask import Flask, request, jsonify, Response, stream_with_context
from flask_cors import CORS
from dotenv import load_dotenv
import os
import subprocess
import requests
app = Flask(__name__)
load_dotenv('/home/gds/printer/.env') # .env 파일 경로
CORS(app, resources={r"/*": {"origins": "*"}}) # 모든 출처에 대해 CORS를 활성화합니다.
# 로그 디렉토리와 파일 설정
log_dir = '/home/gds/printer/log'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
log_file = os.path.join(log_dir, 'app.log')
# 로깅 설정
logging.basicConfig(filename=log_file, level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.DEBUG)
def slice_stl(file_path, output_dir, options=None):
"""
STL 파일을 PrusaSlicer를 사용하여 슬라이스하는 함수
"""
# PrusaSlicer 명령어 생성
prusa_slicer_path = '/usr/bin/PrusaSlicer'
command = [
prusa_slicer_path, '-g',
'--filament-type', options.get('filament_type'),
'--idle-temperature', options.get('idle_temperature'),
'--first-layer-temperature', options.get('first_layer_temperature'),
'--temperature', options.get('temperature'),
'--first-layer-bed-temperature', options.get('first_layer_bed_temperature'),
'--bed-temperature', options.get('bed_temperature'),
'--fill-density', options.get('fill_density')+"%",
'--fill-pattern', options.get('fill_pattern'),
'--infill-anchor', options.get('infill_anchor'),
'--infill-anchor-max', options.get('infill_anchor_max'),
'--top-fill-pattern', options.get('top_fill_pattern'),
'--bottom-fill-pattern', options.get('bottom_fill_pattern'),
'--skirts', options.get('skirts'),
'--skirt-distance', options.get('skirt_distance'),
'--skirt-height', options.get('skirt_height'),
'--draft-shield', options.get('draft_shield'),
'--min-skirt-length', options.get('min_skirt_length'),
'--brim-type', options.get('brim_type'),
'--brim-width', options.get('brim_width'),
'--brim-separation', options.get('brim_separation'),
'--support-material', options.get('support_material'),
'--support-material-auto', options.get('support_material_auto'),
'--support-material-threshold', options.get('support_material_threshold'),
'--support-material-extrusion-width', options.get('support_material_extrusion_width'),
'--raft-first-layer-density', options.get('raft_first_layer_density'),
'--raft-first-layer-expansion', options.get('raft_first_layer_expansion'),
'--perimeter-speed', options.get('perimeter_speed'),
'--small-perimeter-speed', options.get('small_perimeter_speed'),
'--external-perimeter-speed', options.get('external_perimeter_speed'),
'--infill-speed', options.get('infill_speed'),
'--solid-infill-speed', options.get('solid_infill_speed'),
'--top-solid-infill-speed', options.get('top_solid_infill_speed'),
'--support-material-speed', options.get('support_material_speed'),
'--support-material-interface-speed', options.get('support_material_interface_speed'),
'--bridge-speed', options.get('bridge_speed'),
'--gap-fill-speed', options.get('gap_fill_speed'),
'--travel-speed', options.get('travel_speed'),
'--first-layer-speed', options.get('first_layer_speed'),
'--first-layer-speed-over-raft', options.get('first_layer_speed_over_raft'),
f'"{file_path}"'
]
# 출력 디렉토리는 PrusaSlicer가 인식할 수 있는 형식으로 설정
command.extend(['-o', output_dir])
# skirts가 0일 때 관련 옵션 제거
if options.get('skirts') == '0':
command = [opt for idx, opt in enumerate(command) if 'skirt' not in opt and (idx == 0 or 'skirt' not in command[idx - 1])]
command = [opt for idx, opt in enumerate(command) if 'skirts' not in opt and (idx == 0 or 'skirts' not in command[idx - 1])]
# brim-type이 no_brim일 때 관련 옵션 제거
if options.get('brim_type') == 'no_brim':
command = [opt for idx, opt in enumerate(command) if 'brim' not in opt and (idx == 0 or 'brim' not in command[idx - 1])]
command = [opt for idx, opt in enumerate(command) if 'draft' not in opt and (idx == 0 or 'draft' not in command[idx - 1])]
# 이 코드 처리 중에 --support-material 이나 --support-material-auto가 true,false에 따라서 다르게 동작해야 되는데, 둘중 하나 통과하는 부분에서
# command option이 사라진것 같아 확인 필요
i = 0
while i < len(command):
if command[i] == '--support-material' and options.get('support_material') == 'true':
command[i + 1] = '' # Set the next index to empty string
i += 1 # Move to the next index to skip removing '--support-material'
continue
if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'true':
command[i + 1] = '' # Set the next index to empty string
i += 1 # Move to the next index to skip removing '--support-material-auto'
continue
if command[i] == '--support-material' and options.get('support_material') == 'false':
command.pop(i) # Remove '--support-material'
command.pop(i) # Remove the value after '--support-material'
continue
if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'false':
command.pop(i) # Remove '--support-material-auto'
command.pop(i) # Remove the value after '--support-material-auto'
continue
i += 1
app.logger.debug("command : %s", command)
try:
result = subprocess.run(' '.join(command), shell=True, capture_output=True, text=True, check=True)
app.logger.debug("PrusaSlicer output: %s", result.stdout)
return True
except subprocess.CalledProcessError as e:
app.logger.error("PrusaSlicer error: %s", e.stderr)
return False
@app.route('/slice', methods=['POST'])
def slice_model():
if 'file' not in request.files:
app.logger.error('No file part')
return jsonify({'error': 'No file part'})
file = request.files['file']
if file.filename == '':
app.logger.error('No selected file')
return jsonify({'error': 'No selected file'})
# STL 파일을 저장할 경로 설정
upload_dir = '/home/gds/printer/stl'
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
file_path = os.path.join(upload_dir, file.filename)
file.save(file_path)
app.logger.debug('File saved to %s', file_path)
options = {
'filament_type': request.form.get('filament_type'),
'idle_temperature': request.form.get('idle_temperature'),
'first_layer_temperature': request.form.get('first_layer_temperature'),
'temperature': request.form.get('temperature'),
'first_layer_bed_temperature': request.form.get('first_layer_bed_temperature'),
'bed_temperature': request.form.get('bed_temperature'),
'fill_density': request.form.get('fill_density'),
'fill_pattern': request.form.get('fill_pattern'),
'infill_anchor': request.form.get('infill_anchor'),
'infill_anchor_max': request.form.get('infill_anchor_max'),
'top_fill_pattern': request.form.get('top_fill_pattern'),
'bottom_fill_pattern': request.form.get('bottom_fill_pattern'),
'skirts': request.form.get('skirts'),
'skirt_distance': request.form.get('skirt_distance'),
'skirt_height': request.form.get('skirt_height'),
'draft_shield': request.form.get('draft_shield'),
'min_skirt_length': request.form.get('min_skirt_length'),
'brim_type': request.form.get('brim_type'),
'brim_width': request.form.get('brim_width'),
'brim_separation': request.form.get('brim_separation'),
'support_material': request.form.get('support_material'),
'support_material_auto': request.form.get('support_material_auto'),
'support_material_threshold': request.form.get('support_material_threshold'),
'support_material_extrusion_width': request.form.get('support_material_extrusion_width'),
'raft_first_layer_density': request.form.get('raft_first_layer_density'),
'raft_first_layer_expansion': request.form.get('raft_first_layer_expansion'),
'perimeter_speed': request.form.get('perimeter_speed'),
'small_perimeter_speed': request.form.get('small_perimeter_speed'),
'external_perimeter_speed': request.form.get('external_perimeter_speed'),
'infill_speed': request.form.get('infill_speed'),
'solid_infill_speed': request.form.get('solid_infill_speed'),
'top_solid_infill_speed': request.form.get('top_solid_infill_speed'),
'support_material_speed': request.form.get('support_material_speed'),
'support_material_interface_speed': request.form.get('support_material_interface_speed'),
'bridge_speed': request.form.get('bridge_speed'),
'gap_fill_speed': request.form.get('gap_fill_speed'),
'travel_speed': request.form.get('travel_speed'),
'first_layer_speed': request.form.get('first_layer_speed'),
'first_layer_speed_over_raft': request.form.get('first_layer_speed_over_raft')
}
# PrusaSlicer를 사용하여 STL 파일 슬라이스
output_dir = '/home/gds/printer/gcode/'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
success = slice_stl(file_path, output_dir, options)
if success:
# G-code 파일 경로
gcode_file_path = os.path.join(output_dir, os.path.splitext(file.filename)[0] + '.gcode')
return jsonify({'gcode_file_path': gcode_file_path})
else:
return jsonify({'error': 'Failed to slice the model'})
@app.route('/getGCodeFile', methods=['GET'])
def get_gcode_file():
gcode_file_path = request.args.get('gcodeFilePath')
if not gcode_file_path:
return jsonify({'error': 'Missing gcode_file_path parameter'})
try:
with open(gcode_file_path, 'r') as file:
gcode_string = file.read()
return gcode_string
except FileNotFoundError:
return jsonify({'error': 'File not found'})
@app.route('/getPrusaConfig', methods=['GET'])
def get_prusa_config():
config = {
'printer1': {
'url': os.getenv('PRUSA_PRINTER1_API_URL'),
'api_key': os.getenv('PRUSA_PRINTER1_API_KEY')
},
'printer2': {
'url': os.getenv('PRUSA_PRINTER2_API_URL'),
'api_key': os.getenv('PRUSA_PRINTER2_API_KEY')
}
}
return jsonify(config)
@app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy_prusa_link():
try:
# 요청 URL과 파라미터 설정
url = request.args.get('url')
if not url:
return Response("URL is required", status=400)
# PrusaLink에 보낼 요청 설정
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
response = requests.request(
method=request.method,
url=url,
headers=headers,
data=request.get_data(),
cookies=request.cookies,
stream=True # 스트리밍 응답 처리
)
# PrusaLink에서 받은 스트리밍 응답을 클라이언트에 전달
def generate():
for chunk in response.iter_content(chunk_size=8192):
yield chunk
proxy_response = Response(stream_with_context(generate()), status=response.status_code)
for key, value in response.headers.items():
if key.lower() != 'content-encoding' and key.lower() != 'transfer-encoding':
proxy_response.headers[key] = value
return proxy_response
except Exception as e:
return Response(f"An error occurred: {str(e)}", status=500)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)
\ No newline at end of file
import logging
from flask import Flask, request, jsonify, Response, stream_with_context
from flask_cors import CORS
from dotenv import load_dotenv
import os
import subprocess
import requests
app = Flask(__name__)
load_dotenv('/home/gds/printer/.env') # .env 파일 경로
CORS(app, resources={r"/*": {"origins": "*"}}) # 모든 출처에 대해 CORS를 활성화합니다.
# 로그 디렉토리와 파일 설정
log_dir = '/home/gds/printer/log'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
log_file = os.path.join(log_dir, 'app.log')
# 로깅 설정
logging.basicConfig(filename=log_file, level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.DEBUG)
# start_gcode, end_gcode 변수 선언
start_gcode = """
M17
M862.1 P[nozzle_diameter]
M862.3 P "MK4"
M862.5 P2
M862.6 P "Input shaper"
M115 U6.0.1+14848
M555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)}
W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))}
G90
M83
M140 S[first_layer_bed_temperature]
{if filament_notes[0]=~/.*HT_MBL10.*/}
M104 T0 S{first_layer_temperature[0] - 10}
M109 T0 R{first_layer_temperature[0] - 10}
{endif}
{if filament_type[0] == "PC" or filament_type[0] == "PA"}
M104 T0 S{first_layer_temperature[0] - 25}
M109 T0 R{first_layer_temperature[0] - 25}
{endif}
{if filament_type[0] == "FLEX"}
M104 T0 S210
M109 T0 R210
{endif}
{if filament_type[0]=~/.*PET.*/}
M104 T0 S175
M109 T0 R175
{endif}
{if not (filament_notes[0]=~/.*HT_MBL10.*/ or filament_type[0] == "PC" or filament_type[0] == "PA" or filament_type[0] == "FLEX" or filament_type[0]=~/.*PET.*/)}
M104 T0 S170
M109 T0 R170
{endif}
M84 E
G28
G1 X{10 + 32} Y-4 Z5 F4800
M302 S160
{if filament_type[initial_tool]=="FLEX"}
G1 E-4 F2400
{else}
G1 E-2 F2400
{endif}
M84 E
G29 P9 X10 Y-4 W32 H4
{if first_layer_bed_temperature[initial_tool]<=60}M106 S100{endif}
G0 Z40 F10000
M190 S[first_layer_bed_temperature]
M107
M84 E
G29 P1
G29 P1 X0 Y0 W50 H20 C
G29 P3.2
G29 P3.13
G29 A
M104 S{first_layer_temperature[0]}
G0 X0 Y-4 Z15 F4800
M109 S{first_layer_temperature[0]}
G92 E0
M569 S0 E
G92 E0
G1 E{(filament_type[0] == "FLEX" ? 4 : 2)} F2400
G0 E7 X15 Z0.2 F500
G0 X25 E4 F500
G0 X35 E4 F650
G0 X45 E4 F800
G0 X{45 + 3} Z{0.05} F{8000}
G0 X{45 + 3 * 2} Z0.2 F{8000}
G92 E0
M221 S100
"""
end_gcode = """
{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+1, max_print_height)} F720{endif}
M104 S0
M140 S0
M107
G1 X241 Y170 F3600
{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+23, max_print_height)} F300{endif}
G4
M572 S0
M593 X T2 F0
M593 Y T2 F0
M84 X Y E
"""
error_code = ''
err_code =''
def slice_stl(file_path, output_dir, options=None):
"""
STL 파일을 PrusaSlicer를 사용하여 슬라이스하는 함수
"""
# PrusaSlicer 명령어 생성
prusa_slicer_path = '/usr/bin/PrusaSlicer'
command = [
prusa_slicer_path, '-g',
]
# 옵션 추가
for key, value in options.items():
command.extend([f'--{key}', value])
# 파일 경로 추가
command.append(f'"{file_path}"')
# 출력 디렉토리는 PrusaSlicer가 인식할 수 있는 형식으로 설정
command.extend(['-o', output_dir])
# skirts가 0일 때 관련 옵션 제거
if options.get('skirts') == '0':
command = [opt for idx, opt in enumerate(command) if 'skirt' not in opt and (idx == 0 or 'skirt' not in command[idx - 1])]
command = [opt for idx, opt in enumerate(command) if 'skirts' not in opt and (idx == 0 or 'skirts' not in command[idx - 1])]
# brim-type이 no_brim일 때 관련 옵션 제거
if options.get('brim_type') == 'no_brim':
command = [opt for idx, opt in enumerate(command) if 'brim' not in opt and (idx == 0 or 'brim' not in command[idx - 1])]
command = [opt for idx, opt in enumerate(command) if 'draft' not in opt and (idx == 0 or 'draft' not in command[idx - 1])]
# 이 코드 처리 중에 --support-material 이나 --support-material-auto가 true,false에 따라서 다르게 동작해야 되는데, 둘중 하나 통과하는 부분에서
# command option이 사라진것 같아 확인 필요
i = 0
while i < len(command):
if command[i] == '--support-material' and options.get('support_material') == 'true':
command[i + 1] = '' # Set the next index to empty string
i += 1 # Move to the next index to skip removing '--support-material'
continue
if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'true':
command[i + 1] = '' # Set the next index to empty string
i += 1 # Move to the next index to skip removing '--support-material-auto'
continue
if command[i] == '--support-material' and options.get('support_material') == 'false':
command.pop(i) # Remove '--support-material'
command.pop(i) # Remove the value after '--support-material'
continue
if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'false':
command.pop(i) # Remove '--support-material-auto'
command.pop(i) # Remove the value after '--support-material-auto'
continue
i += 1
#app.logger.debug("command : %s", ' '.join(command))
try:
result = subprocess.run(' '.join(command), shell=True, capture_output=True, text=True, check=True)
app.logger.debug("PrusaSlicer output: %s", result.stdout)
return True
except subprocess.CalledProcessError as e:
app.logger.error("PrusaSlicer error: %s", e.stderr)
global error_code
error_code = e.stderr
return False
def parse_slicing_error(error_code):
error_patterns = {
"There is an object with no extrusions in the first layer.": (401, "첫 번째 레이어에 압출물이 없는 객체가 있습니다."),
"No extrusions were generated for objects.": (402, "객체에 대한 압출물이 생성되지 않았습니다."),
"The print is empty. The model is not printable with current print settings.": (403, "출력이 비어 있습니다. 현재 인쇄 설정으로는 모델을 인쇄할 수 없습니다."),
"Levitating objects cannot be printed without supports.": (404, "떠있는 객체는 지지대 없이 인쇄할 수 없습니다."),
"No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.": (405, "레이어가 감지되지 않았습니다. STL 파일을 수리하거나 크기 또는 두께를 확인하고 다시 시도해 보십시오."),
"No pad can be generated for this model with the current configuration": (406, "현재 설정으로는 이 모델에 대해 패드를 생성할 수 없습니다."),
"There are unprintable objects. Try to adjust support settings to make the objects printable.": (407, "인쇄할 수 없는 객체가 있습니다. 객체를 인쇄 가능하게 만들기 위해 지지대 설정을 조정해 보십시오.")
}
for pattern, (code, message) in error_patterns.items():
if pattern in error_code:
return code, message
# 기본 에러 메시지
return 400, "모델 슬라이스에 실패했습니다."
@app.route('/slice', methods=['POST'])
def slice_model():
global error_code
upload_dir = '/home/gds/printer/stl'
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
# 1. file 없을 시 에러 발생
file = request.files.get('file')
if not file:
# STL 파일을 저장할 경로 설정
#2. file이 없어도 was에서 저장시킨 원래 파일명이 있을시
if 'orignlFileNm' in request.form and request.form['orignlFileNm']:
file_path = os.path.join(upload_dir, request.form['orignlFileNm'])
#3. 저장 경로 + 파일명이 존재하지 않을 시
if not os.path.exists(file_path):
app.logger.error('No file in filepath')
return jsonify({'err_code': '200','err_title':'경로상 파일 존재하지 않음','err_msg':'파일이 현재 경로 상에 존재하지 않습니다(/home/gds/printer)'})
else:
app.logger.error('No file part')
return jsonify({'err_code': '100','err_title':'업로드 파일 없음','err_msg':'업로드될 파일이 존재하지 않습니다.'})
else:
file_extension = os.path.splitext(file.filename)[1].lower()
if file_extension != '.stl':
app.logger.error('Invalid file type: %s', file_extension)
return jsonify({'err_code': '300','err_title':'확장자가 다름','err_msg':'STL 파일만 업로드 가능합니다.'})
file_path = os.path.join(upload_dir, file.filename)
file.save(file_path)
app.logger.debug('File saved to %s', file_path)
#form으로 부터 dictionary 형태로 가지고 옴
options = request.form.to_dict()
keys_to_remove = ['options_select_printer','options_select_filament', 'preset-options','wipe-tower-adjustment','menuId','orId','prId','atchFileId1','pageIndex','_csrf','orignlFileNm','atchFileId']
for key in keys_to_remove:
if key in options:
del options[key]
options['filament-colour'] = '"'+options['filament-colour']+'"'
options['fill-density'] = options['fill-density'] + "%"
# 값이 "on"인 항목을 ""로 변경
for key, value in options.items():
if value == 'on' or value == 'true' or value == 'false' :
options[key] = ''
#start-gcode, end-gcode 추가
options['start-gcode'] = f"'{start_gcode}'"
options['end-gcode'] = f"'{end_gcode}'"
# PrusaSlicer를 사용하여 STL 파일 슬라이스
output_dir = '/home/gds/printer/gcode/'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
success = slice_stl(file_path, output_dir, options)
if success:
# G-code 파일 경로
if not file:
gcode_file_path = os.path.join(output_dir,os.path.splitext(request.form['orignlFileNm'])[0]+ '.gcode')
return jsonify({'gcode_file_path': gcode_file_path})
else:
gcode_file_path = os.path.join(output_dir, os.path.splitext(file.filename)[0] + '.gcode')
return jsonify({'gcode_file_path': gcode_file_path})
else:
error_code, error_message = parse_slicing_error(error_code)
return jsonify({'err_code': error_code,'err_title':'슬라이스 에러','err_msg': error_message})
@app.route('/getGCodeFile', methods=['GET'])
def get_gcode_file():
gcode_file_path = request.args.get('gcodeFilePath')
if not gcode_file_path:
return jsonify({'error': 'Missing gcode_file_path parameter'})
try:
with open(gcode_file_path, 'r') as file:
gcode_string = file.read()
return gcode_string
except FileNotFoundError:
return jsonify({'error': 'File not found'})
@app.route('/getPrusaConfig', methods=['GET'])
def get_prusa_config():
config = {
'printer1': {
'url': os.getenv('PRUSA_PRINTER1_API_URL'),
'api_key': os.getenv('PRUSA_PRINTER1_API_KEY')
},
'printer2': {
'url': os.getenv('PRUSA_PRINTER2_API_URL'),
'api_key': os.getenv('PRUSA_PRINTER2_API_KEY')
}
}
return jsonify(config)
@app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy_prusa_link():
try:
# 요청 URL과 파라미터 설정
url = request.args.get('url')
if not url:
return Response("URL is required", status=400)
# PrusaLink에 보낼 요청 설정
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
response = requests.request(
method=request.method,
url=url,
headers=headers,
data=request.get_data(),
cookies=request.cookies,
stream=True # 스트리밍 응답 처리
)
# PrusaLink에서 받은 스트리밍 응답을 클라이언트에 전달
def generate():
for chunk in response.iter_content(chunk_size=8192):
yield chunk
proxy_response = Response(stream_with_context(generate()), status=response.status_code)
for key, value in response.headers.items():
if key.lower() != 'content-encoding' and key.lower() != 'transfer-encoding':
proxy_response.headers[key] = value
return proxy_response
except Exception as e:
return Response(f"An error occurred: {str(e)}", status=500)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)
\ No newline at end of file
Flask
Flask-Cors
python-dotenv
requests
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment