Better version with support of multiple projects

This commit is contained in:
ilia.gurielidze@autStand.com 2025-04-10 04:08:55 +04:00
parent 322d662011
commit 12a85c2fc3
56 changed files with 9359 additions and 1085 deletions

View File

@ -4,3 +4,4 @@ node_modules/
pycache/
cloned_repo/
extracted_texts/
projects/

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

889
app.py
View File

@ -3,541 +3,512 @@ import threading
import time
import json
import git
import csv
import re
from flask import Flask, render_template, jsonify, Response
import re # Import re for project name validation
from flask import Flask, render_template, jsonify, Response, request # Add request
from werkzeug.utils import secure_filename # For securing filenames
from concurrent.futures import ThreadPoolExecutor # Import ThreadPoolExecutor
# Import configurations and new modules
import config
import utils
from manifest_reader import read_manifest
from scada_checker import check_scada
from drawing_checker import check_drawings
from progress_calculator import calculate_combined_progress
app = Flask(__name__)
# --- Configuration ---
REPO_URL = "http://192.168.5.191:3000/LCI/MTN6"
REPO_DIR = "./cloned_repo" # Directory to clone the repo into
BRANCH = "main"
CSV_FILENAME = "MTN6 Equipment Manifest REV6(Conveyor List).csv"
VIEWS_DIR_RELATIVE = "MTN6_SCADA/com.inductiveautomation.perspective/views/Detailed-Views"
TEXT_OUTPUT_FOLDER = "./extracted_texts" # Added: Directory with .txt files
CHECK_INTERVAL_SECONDS = 60
# --- Global state (Per-Project) ---
# Dictionaries keyed by project name
project_last_commit = {}
project_progress_data = {}
project_status = {}
all_projects = utils.discover_projects() # Discover projects at startup
# --- Column Names from CSV (Adjust if necessary) ---
CSV_ALIAS_COL = 'Alias'
CSV_PANEL_COL = 'Control Panel'
CSV_EQ_TYPE_COL = 'Equipment Type' # Optional, for details modal
CSV_CONV_TYPE_COL = 'Type of Conveyor' # Optional, for details modal
# --- Global state ---
last_commit_hash = None
# New detailed progress data structure
progress_data = {
"overall": {
"total_csv": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0,
"percentage_found_both": 0,
"missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []
},
"panels": {} # Populated dynamically
}
status_message = "Initializing..."
repo_lock = threading.Lock() # Lock for accessing repo and shared data
data_updated_event = threading.Event() # Event to signal data updates
# --- Helper Functions ---
def get_repo_path():
return os.path.abspath(REPO_DIR)
def get_csv_path():
script_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(script_dir, CSV_FILENAME)
def get_views_dir_path():
return os.path.join(get_repo_path(), VIEWS_DIR_RELATIVE)
def get_text_output_dir_path():
# Construct absolute path based on the script's directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Use os.path.join to handle path separators correctly and avoid './'
return os.path.abspath(os.path.join(script_dir, TEXT_OUTPUT_FOLDER))
def normalize(text):
"""Normalize string for comparison: lowercase, treat '-' and '_' the same, remove all whitespace."""
if not isinstance(text, str):
return ""
text = text.lower() # Convert to lowercase
text = text.replace('-', '_') # Replace hyphens with underscores
text = re.sub(r'\s+', '', text) # Remove ALL whitespace characters (including newlines)
return text
def read_manifest(csv_filepath):
"""Reads the manifest CSV into a list of dictionaries."""
manifest_items = []
# Only require Alias and Panel now for basic grouping
required_cols = {CSV_ALIAS_COL, CSV_PANEL_COL}
optional_cols = {CSV_EQ_TYPE_COL, CSV_CONV_TYPE_COL}
try:
# Revert back to 'utf-8-sig' to handle potential BOM from Excel
with open(csv_filepath, mode='r', newline='', encoding='utf-8-sig') as infile:
reader = csv.DictReader(infile)
headers = set(h.strip() for h in reader.fieldnames)
# Check for required columns
missing_required = required_cols - headers
if missing_required:
print(f"Error: Missing required columns in CSV '{csv_filepath}': {', '.join(missing_required)}")
print(f"Available columns: {', '.join(headers)}")
return None
for row in reader:
alias = row.get(CSV_ALIAS_COL, "").strip()
panel = row.get(CSV_PANEL_COL, "").strip()
# unit_number = row.get('Unit Number', "").strip() # No longer needed for filename
# Add if Alias and Control Panel are present (Panel needed for grouping results later)
if alias and panel:
item = {
"alias": alias,
"normalized_alias": normalize(alias),
"control_panel": panel,
# "unit_number": unit_number, # Removed
# "expected_drawing_filename": f"MTN6_SYSDL-{unit_number}.txt", # Removed
# Add optional data if columns exist
"equipment_type": row.get(CSV_EQ_TYPE_COL, "").strip() if CSV_EQ_TYPE_COL in headers else "N/A",
"conveyor_type": row.get(CSV_CONV_TYPE_COL, "").strip() if CSV_CONV_TYPE_COL in headers else "N/A",
# Status fields to be filled later
"found_scada": False,
"found_drawing": False
}
manifest_items.append(item)
# elif alias and panel: # If Unit Number is missing but others are present # Condition removed
# print(f"Warning: Alias '{alias}' in Panel '{panel}' is missing 'Unit Number' in CSV. Skipping drawing check for this item.")
elif alias and not panel:
print(f"Warning: Alias '{alias}' found in CSV but is missing its '{CSV_PANEL_COL}'. Skipping.")
# Add other specific warnings if needed
except FileNotFoundError:
print(f"Error: Manifest file not found at {csv_filepath}")
return None
except Exception as e:
print(f"Error reading CSV file {csv_filepath}: {e}")
return None
print(f"Read {len(manifest_items)} valid items from manifest.")
return manifest_items
def check_scada(manifest_data, views_dir):
"""Checks for aliases in SCADA JSON view files."""
if not manifest_data: return
print(f"Starting SCADA check in directory: {views_dir}...")
found_count = 0
processed_files = 0
# Create a quick lookup map of normalized_alias -> list of manifest items (handles duplicate aliases)
alias_map = {}
for item in manifest_data:
na = item['normalized_alias']
if na not in alias_map:
alias_map[na] = []
alias_map[na].append(item)
try:
for root, _, files in os.walk(views_dir):
for filename in files:
if filename == 'view.json':
filepath = os.path.join(root, filename)
processed_files += 1
try:
with open(filepath, 'r', encoding='utf-8') as f:
# Read the whole file, normalize it for substring search
content = f.read()
normalized_content = normalize(content)
# Check manifest aliases against this file's normalized content
for norm_alias, items in alias_map.items():
if norm_alias in normalized_content:
for item in items:
if not item['found_scada']: # Update only if not already found elsewhere
item['found_scada'] = True
found_count += 1 # Count unique aliases found
except Exception as e:
print(f" Warning: Could not read or process JSON file {filepath}: {e}")
except Exception as e:
print(f"Error walking SCADA views directory {views_dir}: {e}")
print(f"SCADA check finished. Processed {processed_files} view.json files. Found {found_count} manifest aliases.")
def check_drawings(manifest_data, text_output_dir):
"""Checks if aliases from manifest exist in *any* extracted drawing text file."""
if not manifest_data: return
print(f"Starting Drawings check: Scanning all .txt files in directory: {text_output_dir}...")
all_normalized_content = "" # Combine all text content here
processed_files = 0
found_files = []
try:
# Step 1: Read and combine content of all .txt files in the directory
for filename in os.listdir(text_output_dir):
if filename.lower().endswith('.txt'):
filepath = os.path.join(text_output_dir, filename)
processed_files += 1
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Add a separator to prevent false matches across file boundaries
all_normalized_content += normalize(content) + "\n--file-separator--\n"
found_files.append(filename)
except Exception as e:
print(f" Warning: Could not read or process text file {filepath}: {e}")
if processed_files == 0:
print(" Warning: No .txt files found in the directory. Cannot perform drawing check.")
return
else:
print(f" Successfully read and normalized content from {len(found_files)} out of {processed_files} .txt files found.")
# Step 2: Check each manifest alias against the combined content
found_count = 0
for item in manifest_data:
normalized_alias = item['normalized_alias']
if normalized_alias and normalized_alias in all_normalized_content:
item['found_drawing'] = True
found_count += 1
# else: item['found_drawing'] is already False by default
print(f"Drawings check finished. Found {found_count} manifest aliases within the combined text content.")
except FileNotFoundError:
print(f" Error: Drawings text directory not found: {text_output_dir}")
except Exception as e:
print(f" Error during drawings check: {e}")
def calculate_combined_progress(manifest_data):
"""Calculates the combined progress based on scada/drawing status."""
print("Calculating combined progress statistics...")
results = {
"overall": {
"total_csv": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0,
"percentage_found_both": 0,
"missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []
},
# Initialize state for discovered projects
def get_default_progress():
# Helper to return a fresh copy of the default progress structure
return {
"overall": {"total_csv": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0, "percentage_found_both": 0, "missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []},
"panels": {}
}
if not manifest_data:
print("Warning: No manifest data to calculate progress from.")
return results
results["overall"]["total_csv"] = len(manifest_data)
for proj_name in all_projects:
project_last_commit[proj_name] = None
project_progress_data[proj_name] = get_default_progress()
project_status[proj_name] = "Initializing..."
for item in manifest_data:
panel = item['control_panel']
repo_lock = threading.Lock() # Lock remains global for now, managing access to shared dicts
data_updated_event = threading.Event() # Event signals ANY project update
# Initialize panel data if not present
if panel not in results["panels"]:
results["panels"][panel] = {
"total": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0,
"percentage_found_both": 0,
"missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []
}
# Define max workers for thread pools
MAX_INITIAL_CHECK_WORKERS = 5 # Adjust as needed
MAX_PERIODIC_CHECK_WORKERS = 5 # Adjust as needed
results["panels"][panel]["total"] += 1
# --- Core Logic Orchestration (Per-Project) ---
# Categorize and add to lists
item_detail = {k: v for k, v in item.items() if k not in ['normalized_alias']} # Don't need normalized in output
if item['found_scada'] and item['found_drawing']:
results["overall"]["found_both"] += 1
results["panels"][panel]["found_both"] += 1
results["overall"]["found_both_list"].append(item_detail)
results["panels"][panel]["found_both_list"].append(item_detail)
elif item['found_scada'] and not item['found_drawing']:
results["overall"]["found_scada_only"] += 1
results["panels"][panel]["found_scada_only"] += 1
results["overall"]["found_scada_only_list"].append(item_detail)
results["panels"][panel]["found_scada_only_list"].append(item_detail)
elif not item['found_scada'] and item['found_drawing']:
results["overall"]["found_drawing_only"] += 1
results["panels"][panel]["found_drawing_only"] += 1
results["overall"]["found_drawing_only_list"].append(item_detail)
results["panels"][panel]["found_drawing_only_list"].append(item_detail)
else: # Missing both
results["overall"]["missing_both"] += 1
results["panels"][panel]["missing_both"] += 1
results["overall"]["missing_list"].append(item_detail)
results["panels"][panel]["missing_list"].append(item_detail)
# Calculate percentages
if results["overall"]["total_csv"] > 0:
results["overall"]["percentage_found_both"] = round(
(results["overall"]["found_both"] / results["overall"]["total_csv"]) * 100, 1
)
for panel_data in results["panels"].values():
if panel_data["total"] > 0:
panel_data["percentage_found_both"] = round(
(panel_data["found_both"] / panel_data["total"]) * 100, 1
)
print("Combined progress calculation finished.")
# print(json.dumps(results, indent=2)) # DEBUG: Print structure
return results
# --- Core Logic ---
def update_progress_data():
"""Reads manifest, runs both checks, combines results, and updates global state."""
global progress_data, status_message
csv_path = get_csv_path()
views_dir = get_views_dir_path()
text_dir = get_text_output_dir_path()
def update_progress_data(project_name):
"""Reads manifest, runs checks, combines results for a specific project."""
global project_progress_data, project_status # Reference the global dicts
current_status = ""
new_data_calculated = None
print(f"[{project_name}] Starting analysis workflow...")
# 1. Read Manifest
status_message = "Reading manifest file..."
print(f"Reading manifest: {csv_path}")
manifest_data = read_manifest(csv_path)
set_status(project_name, "Reading manifest file...")
manifest_data = read_manifest(project_name)
if manifest_data is None:
current_status = f"Error: Failed to read or process manifest file {csv_path}"
current_status = f"[{project_name}] Error: Failed to read or process manifest file."
print(current_status)
status_message = current_status
data_updated_event.set(); data_updated_event.clear()
set_status(project_name, current_status)
# Reset progress data for this project on manifest error
with repo_lock:
project_progress_data[project_name] = get_default_progress()
data_updated_event.set(); data_updated_event.clear() # Signal update (error status + reset data)
return # Cannot proceed without manifest
# 2. Check SCADA (JSON files)
status_message = "Checking SCADA views..."
if not os.path.exists(views_dir):
current_status = f"Warning: SCADA Views directory not found at {views_dir}. Skipping SCADA check."
print(current_status)
# Mark all as not found in SCADA? Or just skip update? Skipping update is safer.
else:
check_scada(manifest_data, views_dir)
set_status(project_name, "Checking SCADA views...")
check_scada(project_name, manifest_data)
# 3. Check Drawings (TXT files)
status_message = "Checking drawing text files..."
if not os.path.exists(text_dir):
current_status = f"Warning: Extracted Text directory not found at {text_dir}. Skipping Drawings check."
print(current_status)
# Mark all as not found in Drawings? Or skip? Skipping update.
else:
check_drawings(manifest_data, text_dir)
set_status(project_name, "Checking drawing text files...")
check_drawings(project_name, manifest_data)
# 4. Calculate Combined Progress
status_message = "Calculating combined progress..."
set_status(project_name, "Calculating combined progress...")
try:
new_data_calculated = calculate_combined_progress(manifest_data)
new_data_calculated = calculate_combined_progress(project_name, manifest_data)
if new_data_calculated:
current_status = f"Analysis complete at {time.strftime('%Y-%m-%d %H:%M:%S')}"
current_status = f"[{project_name}] Analysis complete at {time.strftime('%Y-%m-%d %H:%M:%S')}"
else:
# This case shouldn't happen if manifest_data was valid
current_status = "Error: Failed to calculate combined progress."
current_status = f"[{project_name}] Warning: Progress calculation yielded no results (manifest might be empty)."
new_data_calculated = get_default_progress() # Reset to default empty structure
except Exception as e:
current_status = f"Error during progress calculation: {e}"
print(f"Detailed Calculation Error: {e}", exc_info=True) # Log stack trace
current_status = f"[{project_name}] Error during progress calculation: {e}"
print(f"Detailed Calculation Error: {e}") # Log stack trace (removed exc_info)
new_data_calculated = None # Ensure no partial data update
# Update global state
print(current_status)
status_message = current_status # Update status regardless of calculation success/failure
if new_data_calculated is not None:
progress_data = new_data_calculated
# Signal that an update attempt finished WITH new data
# Update global state atomically for this project
with repo_lock:
print(current_status)
# Update status first (always)
project_status[project_name] = current_status
# Update progress data only if calculation was successful or yielded default empty
if new_data_calculated is not None:
project_progress_data[project_name] = new_data_calculated
# Signal update regardless of calculation success if status changed or data changed
data_updated_event.set()
data_updated_event.clear()
# --- Git Repo Handling (Modified slightly to use updated status messages) ---
def check_and_update_repo():
global last_commit_hash, status_message
repo_path = get_repo_path()
did_update = False # Flag to track if files were actually updated
initial_hash = last_commit_hash # Store hash before check
def set_status(project_name, message):
"""Helper to update status message for a project and signal change."""
global project_status
with repo_lock:
try:
repo_existed = os.path.exists(os.path.join(repo_path, ".git"))
if not repo_existed:
print(f"Cloning repository {REPO_URL} into {repo_path}...")
status_message = f"Cloning repository {REPO_URL}..."
git.Repo.clone_from(REPO_URL, repo_path, branch=BRANCH)
repo = git.Repo(repo_path)
last_commit_hash = repo.head.commit.hexsha
print(f"Initial clone complete. Commit: {last_commit_hash}")
did_update = True # Cloned, so considered an update
else:
repo = git.Repo(repo_path)
print("Fetching updates from remote...")
current_local_commit = repo.head.commit.hexsha
# Update hash *before* fetch in case fetch fails but commit was readable
if last_commit_hash is None: last_commit_hash = current_local_commit
origin = repo.remotes.origin
fetch_info = origin.fetch()
if project_status.get(project_name) != message:
print(f"[{project_name}] Status: {message}")
project_status[project_name] = message
data_updated_event.set()
data_updated_event.clear()
# Check if fetch actually brought new data for the target branch
# fetched_new_commits = any(info.flags & info.NEW_HEAD for info in fetch_info if info.name == f'origin/{BRANCH}') # More precise check if needed
# --- Git Repo Handling (Per-Project) ---
current_remote_commit = repo.commit(f'origin/{BRANCH}').hexsha
def check_and_update_repo(project_name):
"""Checks and updates the Git repository for a specific project, minimizing lock contention."""
global project_last_commit, project_status # Reference global dicts
print(f"Local commit: {current_local_commit}, Remote commit: {current_remote_commit}")
repo_path = utils.get_repo_path(project_name)
repo_url = config.REPO_URL # Assuming global for now
branch = config.BRANCH # Assuming global for now
if current_local_commit != current_remote_commit:
print("New commit detected! Pulling changes...")
status_message = "Pulling updates..."
try:
pull_info = origin.pull()
new_commit_hash = repo.head.commit.hexsha
print(f"Pull successful. New commit: {new_commit_hash}")
last_commit_hash = new_commit_hash
did_update = True # Pulled, so considered an update
except git.GitCommandError as e:
status_message = f"Error pulling repository: {e}"
print(status_message)
# Revert hash if pull failed
last_commit_hash = current_local_commit
else:
print("No new commits detected.")
# Update status if it wasn't an error before
if not status_message.startswith("Error"):
status_message = f"Checked repo at {time.strftime('%Y-%m-%d %H:%M:%S')}. No changes."
did_update = False # Flag to track if files were actually updated
initial_hash = None
with repo_lock: # Briefly lock to get initial hash
initial_hash = project_last_commit.get(project_name)
# Run analysis IF the repo was updated (cloned or pulled)
if did_update:
# Status will be updated within update_progress_data
update_progress_data()
# If no git update, signal any status change (e.g., "No changes" or error)
# else: # REMOVED block that signaled event for no changes
# REMOVED: data_updated_event.set() # Signal status change event
# REMOVED: data_updated_event.clear()
# Status message is still updated globally, just won't trigger event
try:
project_base_path = utils.get_project_base_path(project_name)
if not os.path.exists(project_base_path):
# Use set_status which handles locking
set_status(project_name, f"Error: Project directory not found: {project_base_path}")
return False # Cannot proceed
except git.GitCommandError as e:
status_message = f"Git command error: {e}"
print(status_message)
# Try to get commit hash even if failed
# Ensure parent directory exists (outside lock)
os.makedirs(os.path.dirname(repo_path), exist_ok=True)
repo_existed = os.path.exists(os.path.join(repo_path, ".git"))
if not repo_existed:
print(f"[{project_name}] Cloning repository {repo_url} into {repo_path}...")
set_status(project_name, "Cloning repository...")
# --- Clone happens OUTSIDE lock ---
try:
if os.path.exists(os.path.join(repo_path, ".git")):
repo = git.Repo(repo_path)
# Use previous hash if available, else try to read current
if last_commit_hash is None: last_commit_hash = repo.head.commit.hexsha
except Exception:
if last_commit_hash is None: last_commit_hash = "Error reading commit"
# REMOVED: data_updated_event.set() # Signal error status change
# REMOVED: data_updated_event.clear()
except Exception as e:
status_message = f"Error checking repository: {e}"
print(status_message)
if last_commit_hash is None: last_commit_hash = "Error checking repo"
# REMOVED: data_updated_event.set() # Signal error status change
# REMOVED: data_updated_event.clear()
git.Repo.clone_from(repo_url, repo_path, branch=branch)
repo = git.Repo(repo_path)
new_commit_hash = repo.head.commit.hexsha
with repo_lock: # Lock ONLY to update shared state
project_last_commit[project_name] = new_commit_hash
print(f"[{project_name}] Initial clone complete. Commit: {new_commit_hash}")
did_update = True
except git.GitCommandError as clone_err:
set_status(project_name, f"Error cloning repository: {clone_err}")
print(f"[{project_name}] Git clone error: {clone_err}")
# Ensure commit state reflects error if needed
with repo_lock:
if project_last_commit.get(project_name) is None:
project_last_commit[project_name] = "Clone Error"
return False # Indicate no update occurred
# --- End Clone ---
else:
# --- Fetch/Pull Logic ---
repo = git.Repo(repo_path)
current_local_commit = repo.head.commit.hexsha
# Ensure initial hash is set if missing (brief lock)
with repo_lock:
if project_last_commit.get(project_name) is None:
project_last_commit[project_name] = current_local_commit
initial_hash = current_local_commit # Update local var too
print(f"[{project_name}] Fetching updates from remote...")
set_status(project_name, "Checking for updates...")
origin = repo.remotes.origin
# --- Fetch happens OUTSIDE lock ---
try:
fetch_info = origin.fetch()
except git.GitCommandError as fetch_err:
set_status(project_name, f"Error fetching remote: {fetch_err}")
print(f"[{project_name}] Git fetch error: {fetch_err}")
return False # No update occurred
# --- End Fetch ---
# --- Check commits (brief lock) ---
current_remote_commit = None
pull_needed = False
try:
# Must read remote commit *after* fetch
current_remote_commit = repo.commit(f'origin/{branch}').hexsha
# Check if pull is needed inside the try block after getting remote commit
if current_local_commit != current_remote_commit:
pull_needed = True
except git.GitCommandError as commit_err:
set_status(project_name, f"Error accessing remote branch origin/{branch}: {commit_err}")
print(f"[{project_name}] Error accessing remote branch: {commit_err}")
return False # Cannot compare/pull
# --- End Check commits ---
print(f"[{project_name}] Local commit: {current_local_commit}, Remote commit (origin/{branch}): {current_remote_commit}")
if pull_needed:
print(f"[{project_name}] New commit detected! Pulling changes...")
set_status(project_name, "Pulling updates...")
# --- Pull happens OUTSIDE lock ---
try:
pull_info = origin.pull()
new_commit_hash = repo.head.commit.hexsha # Get hash after pull
with repo_lock: # Lock ONLY to update shared state
project_last_commit[project_name] = new_commit_hash
print(f"[{project_name}] Pull successful. New commit: {new_commit_hash}")
did_update = True
except git.GitCommandError as pull_err:
set_status(project_name, f"Error pulling repository: {pull_err}")
print(f"[{project_name}] Git pull error: {pull_err}")
# Revert shared state hash if pull failed? Safest is to keep the pre-pull local commit.
with repo_lock:
project_last_commit[project_name] = current_local_commit # Revert to known local state before pull attempt
# Keep did_update = False
# --- End Pull ---
else:
print(f"[{project_name}] No new commits detected.")
# Update status only if it wasn't an error before (set_status handles lock)
current_status = project_status.get(project_name, "")
if not current_status.startswith("Error"):
set_status(project_name, f"Checked repo at {time.strftime('%Y-%m-%d %H:%M:%S')}. No changes.")
# --- End Fetch/Pull Logic ---
# --- Run analysis IF repo was updated (outside lock) ---
if did_update:
print(f"[{project_name}] Repository updated. Triggering analysis...")
update_progress_data(project_name) # Calls the orchestrator function
except git.InvalidGitRepositoryError:
msg = f"Error: Directory '{repo_path}' exists but is not a valid Git repository. Consider deleting it and restarting."
set_status(project_name, msg) # Handles lock
print(f"[{project_name}] {msg}")
with repo_lock: # Lock to update commit state
project_last_commit[project_name] = "Invalid Repository"
except git.GitCommandError as e:
# General Git command error (if not caught above)
msg = f"Git command error: {e}"
set_status(project_name, msg) # Handles lock
print(f"[{project_name}] {msg}")
# Try to set commit hash state even on error (brief lock)
with repo_lock:
if project_last_commit.get(project_name) is None: # Only set if not already set (e.g., by failed pull)
try:
if os.path.exists(os.path.join(repo_path, ".git")):
repo = git.Repo(repo_path)
project_last_commit[project_name] = repo.head.commit.hexsha
else:
project_last_commit[project_name] = "Error (No repo)"
except Exception:
project_last_commit[project_name] = "Error reading commit"
except Exception as e:
# Catch-all for other unexpected errors
msg = f"Unexpected error checking repository: {e}"
set_status(project_name, msg) # Handles lock
print(f"[{project_name}] {msg}") # Log stack trace for unexpected errors
with repo_lock: # Lock to update commit state
if project_last_commit.get(project_name) is None:
project_last_commit[project_name] = "Error checking repo"
# Return true if analysis was run (because repo changed), false otherwise
return did_update
def periodic_repo_check():
"""Runs the check_and_update_repo function periodically."""
while True:
print(f"\nStarting periodic repository check (Interval: {CHECK_INTERVAL_SECONDS}s)...")
repo_changed = check_and_update_repo()
# If repo didn't change, analysis wasn't triggered, but we might want to run it anyway?
# For now, analysis only runs if repo changes or on initial startup.
# If you want analysis *every* interval regardless of git changes, add a call here:
# if not repo_changed:
# print("Repo unchanged, triggering analysis anyway...")
# update_progress_data()
print(f"Check finished. Sleeping...")
time.sleep(CHECK_INTERVAL_SECONDS)
"""Runs the check_and_update_repo function periodically for all projects using a thread pool."""
global all_projects
# Use a ThreadPoolExecutor to manage periodic checks concurrently
with ThreadPoolExecutor(max_workers=MAX_PERIODIC_CHECK_WORKERS) as executor:
while True:
print(f"\nStarting periodic check cycle for all projects (Interval: {config.CHECK_INTERVAL_SECONDS}s)...")
current_projects = list(all_projects) # Copy list in case it changes
# --- Flask Routes (Largely unchanged, rely on updated global state) ---
futures = []
for project_name in current_projects:
print(f"--- Submitting periodic check for project: {project_name} ---")
# Submit check_and_update_repo to the thread pool
futures.append(executor.submit(run_check_and_log_errors, project_name, "periodic"))
# Wait briefly for tasks to start, but don't block the loop long
# time.sleep(1) # Optional: short sleep if needed
print(f"Periodic check cycle submitted. Sleeping for {config.CHECK_INTERVAL_SECONDS}s...")
time.sleep(config.CHECK_INTERVAL_SECONDS)
# Note: We don't explicitly wait for futures to complete here.
# The pool manages threads, and the loop continues periodically.
def run_check_and_log_errors(project_name, check_type="initial"):
"""Wrapper to run check_and_update_repo and log any exceptions."""
try:
print(f"--- [{check_type.capitalize()}] Running check for project: {project_name} ---")
check_and_update_repo(project_name)
print(f"--- [{check_type.capitalize()}] Finished check for project: {project_name} ---")
except Exception as e:
err_msg = f"Critical error during {check_type} check for {project_name}: {e}"
print(err_msg)
# Use set_status which handles locking and event signaling
set_status(project_name, f"Error during {check_type} check: {e}")
def initial_project_setup_and_analysis(project_name):
"""Performs initial repo check/update AND ensures initial analysis runs."""
try:
print(f"--- [Initial Setup] Starting for project: {project_name} ---")
# Run check_and_update_repo first. It returns True if it triggered an update/analysis.
update_occurred = check_and_update_repo(project_name)
# If no update occurred (repo was cloned before or was already up-to-date),
# we still need to run the analysis once on startup.
if not update_occurred:
print(f"--- [Initial Analysis] Repo up-to-date or non-git. Running analysis for project: {project_name} ---")
update_progress_data(project_name) # Run the analysis explicitly
print(f"--- [Initial Setup] Finished for project: {project_name} ---")
except Exception as e:
err_msg = f"Critical error during initial setup/analysis for {project_name}: {e}"
print(err_msg)
set_status(project_name, f"Error during initial setup: {e}")
# --- Flask Routes ---
@app.route('/')
def index():
return render_template('index.html')
# Pass the list of projects and initial statuses to the template
with repo_lock:
initial_statuses = dict(project_status) # Get a consistent snapshot
project_list = list(all_projects)
return render_template('index.html', projects=project_list, initial_statuses=initial_statuses)
@app.route('/drawings')
def drawings_page():
# Render the main index template which now contains all content
return render_template('index.html')
@app.route('/conflicts')
def conflicts_page():
# Render the main index template which now contains all content
return render_template('index.html')
# Removed redundant routes for /drawings and /conflicts as index.html handles tabs
@app.route('/stream')
def stream():
def event_stream():
last_sent_hash_to_client = None # Track hash sent to *this specific client*
# Track state sent to *this specific client* (using a copy of global state)
last_sent_state = {}
# Send initial state immediately on connection
with repo_lock:
current_global_hash = last_commit_hash
current_global_status = status_message
current_global_progress = progress_data
# Send data for all known projects
current_global_state = {
"projects": list(all_projects),
"status": dict(project_status),
"progress": dict(project_progress_data),
"last_commit": dict(project_last_commit)
}
initial_payload = json.dumps({
"status": current_global_status,
"progress": current_global_progress,
"last_commit": current_global_hash
})
initial_payload = json.dumps(current_global_state)
yield f"data: {initial_payload}\n\n"
last_sent_hash_to_client = current_global_hash # Record that we sent the initial state for this client
print(f"Sent initial state to new client (Hash: {last_sent_hash_to_client})")
last_sent_state = current_global_state # Store the state sent to this client
print(f"Sent initial state to new client for projects: {last_sent_state.get('projects')}")
# Now wait for subsequent updates signaled by the event
while True:
data_updated_event.wait() # Wait for background thread to signal completion
data_updated_event.wait() # Wait for ANY background thread signal
with repo_lock: # Re-acquire lock to get the latest state
current_global_hash = last_commit_hash
current_global_status = status_message
current_global_progress = progress_data
current_global_state = {
"projects": list(all_projects),
"status": dict(project_status),
"progress": dict(project_progress_data),
"last_commit": dict(project_last_commit)
}
# Send update to the client IF the data is different from what they last received
# Check hash first as primary indicator of change in underlying data
if current_global_hash != last_sent_hash_to_client:
print(f"Data updated (Hash changed: {last_sent_hash_to_client} -> {current_global_hash}). Sending update to client.")
data_payload = json.dumps({
"status": current_global_status,
"progress": current_global_progress,
"last_commit": current_global_hash
})
yield f"data: {data_payload}\n\n"
last_sent_hash_to_client = current_global_hash # Update the hash sent to this client
# else: # No need for the else block logging here anymore, as the event shouldn't trigger if hash is same
# If hash is the same, maybe only the status message changed (e.g., error occurred)
# Option: Send update only if status is different from last sent status?
# For simplicity now, we only send if hash differs. Client UI shows last known status.
# print(f"Data updated event triggered, but hash {current_global_hash} unchanged for this client. Status: '{current_global_status}'") # Removed log
# Basic check: Compare entire state dictionaries (can be refined if needed)
# Using json.dumps for a quick deep comparison, might be slow for huge data
current_state_json = json.dumps(current_global_state, sort_keys=True)
last_sent_state_json = json.dumps(last_sent_state, sort_keys=True)
if current_state_json != last_sent_state_json:
print(f"Global state changed. Sending update to client.")
# print(f"Debug: Old state: {last_sent_state_json}") # Optional debug
# print(f"Debug: New state: {current_state_json}") # Optional debug
yield f"data: {current_state_json}\n\n"
last_sent_state = current_global_state # Update the state sent to this client
# else: # Log if event triggered but nothing changed
# print(f"Data update event triggered, but state unchanged for this client.")
return Response(event_stream(), mimetype="text/event-stream")
# --- NEW: Add Project Endpoint ---
ALLOWED_PROJECT_NAME_REGEX = re.compile(r'^[a-zA-Z0-9_-]+$')
@app.route('/add_project', methods=['POST'])
def add_project():
if 'projectName' not in request.form:
return jsonify(success=False, message="Missing project name."), 400
if 'repoUrl' not in request.form: # We receive it but don't use it for cloning yet
return jsonify(success=False, message="Missing repository URL."), 400
if 'manifestFile' not in request.files:
return jsonify(success=False, message="Missing manifest CSV file."), 400
project_name_raw = request.form['projectName'].strip()
repo_url = request.form['repoUrl'].strip()
manifest_file = request.files['manifestFile']
pdf_files = request.files.getlist('pdfFiles') # Use getlist for multiple files
# --- Validation ---
if not project_name_raw:
return jsonify(success=False, message="Project name cannot be empty."), 400
if not ALLOWED_PROJECT_NAME_REGEX.match(project_name_raw):
return jsonify(success=False, message="Invalid Project Name. Use only letters, numbers, underscores, or hyphens."), 400
if not manifest_file.filename or not manifest_file.filename.lower().endswith('.csv'):
return jsonify(success=False, message="Manifest file must be a .csv file."), 400
if not pdf_files or all(not f.filename for f in pdf_files): # Check if list is empty or contains only empty filenames
return jsonify(success=False, message="At least one PDF file must be provided."), 400
for pdf_file in pdf_files:
if not pdf_file.filename or not pdf_file.filename.lower().endswith('.pdf'):
return jsonify(success=False, message=f"Invalid file type uploaded: {pdf_file.filename}. Only PDF files allowed."), 400
# Use secure_filename for the project name used in paths
# Although we validated with regex, this adds another layer against path traversal etc.
safe_project_name = secure_filename(project_name_raw)
if safe_project_name != project_name_raw: # Extra check if secure_filename modified it unexpectedly (e.g., spaces removed)
print(f"Warning: Project name sanitized from '{project_name_raw}' to '{safe_project_name}'")
# Optionally reject here, or proceed with the sanitized name
project_base_path = os.path.join(config.PROJECTS_ROOT_DIR, safe_project_name)
pdf_dir_path = os.path.join(project_base_path, 'pdfs')
repo_dir_path = os.path.join(project_base_path, 'repo') # Create repo dir, but don't clone yet
# --- Check if project already exists ---
if os.path.exists(project_base_path):
return jsonify(success=False, message=f"Project '{safe_project_name}' already exists."), 400
# --- Create Directories ---
try:
print(f"Creating directory structure for project: {safe_project_name}")
os.makedirs(project_base_path, exist_ok=False) # Base dir first, fail if exists
os.makedirs(pdf_dir_path, exist_ok=True)
os.makedirs(repo_dir_path, exist_ok=True)
except OSError as e:
print(f"Error creating directories for {safe_project_name}: {e}")
return jsonify(success=False, message=f"Server error creating project directories: {e}"), 500
# --- Save Manifest File ---
try:
manifest_filename = secure_filename(manifest_file.filename)
manifest_save_path = os.path.join(project_base_path, manifest_filename)
print(f"Saving manifest file to: {manifest_save_path}")
manifest_file.save(manifest_save_path)
except Exception as e:
print(f"Error saving manifest file for {safe_project_name}: {e}")
# Clean up created directories on error?
# shutil.rmtree(project_base_path, ignore_errors=True)
return jsonify(success=False, message=f"Error saving manifest file: {e}"), 500
# --- Save PDF Files ---
saved_pdfs = []
try:
for pdf_file in pdf_files:
if pdf_file and pdf_file.filename: # Check again if file is valid
pdf_filename = secure_filename(pdf_file.filename)
pdf_save_path = os.path.join(pdf_dir_path, pdf_filename)
print(f"Saving PDF file to: {pdf_save_path}")
pdf_file.save(pdf_save_path)
saved_pdfs.append(pdf_filename)
except Exception as e:
print(f"Error saving PDF files for {safe_project_name}: {e}")
# Clean up potentially partially saved files and directories?
# shutil.rmtree(project_base_path, ignore_errors=True)
return jsonify(success=False, message=f"Error saving PDF files: {e}"), 500
# --- Store Repo URL (optional, e.g., in a simple info file) ---
try:
info_file_path = os.path.join(project_base_path, 'project_info.txt')
with open(info_file_path, 'w') as f:
f.write(f"ProjectName: {safe_project_name}\n")
f.write(f"RepoURL: {repo_url}\n")
print(f"Saved project info (including repo URL) to: {info_file_path}")
except Exception as e:
print(f"Warning: Could not save project_info.txt for {safe_project_name}: {e}")
# Don't treat this as a fatal error for the add operation itself
print(f"Successfully added project '{safe_project_name}' with {len(saved_pdfs)} PDF(s).")
# NOTE: Server needs restart for this new project to be discovered and processed.
return jsonify(success=True, message=f"Project '{safe_project_name}' created successfully.")
# --- Main Execution ---
if __name__ == '__main__':
# Ensure repo and text directories exist (optional for text dir if PDFs are pre-processed)
if not os.path.exists(REPO_DIR):
os.makedirs(REPO_DIR)
if not os.path.exists(TEXT_OUTPUT_FOLDER):
print(f"Warning: Text output folder '{TEXT_OUTPUT_FOLDER}' not found. Drawing check might fail unless PDF extraction runs first or files are manually placed.")
# os.makedirs(TEXT_OUTPUT_FOLDER) # Optionally create it
# Ensure project-specific directories (like text output) exist if needed
# This is now handled within drawing_checker
# Perform initial check/clone and data load
print("Performing initial repository check and data load...")
# Run check_and_update_repo which calls update_progress_data if repo updated
initial_update_done = check_and_update_repo()
# If repo existed and was up-to-date on first check, analysis wasn't run yet. Run it now.
if not initial_update_done:
print("Repository present and up-to-date. Running initial analysis...")
# No need for lock here as background thread isn't running yet
update_progress_data() # Run the full analysis
# Perform initial check/clone and data load FOR EACH PROJECT in parallel
print("--- Performing initial checks and analysis for all discovered projects in background threads ---")
if not all_projects:
print("Warning: No projects discovered in projects directory.")
else:
print("Initial analysis was triggered by repo clone/pull.")
# Use a ThreadPoolExecutor for initial setup
with ThreadPoolExecutor(max_workers=MAX_INITIAL_CHECK_WORKERS, thread_name_prefix='InitialCheck') as executor:
for proj_name in all_projects:
print(f"--- Submitting initial setup for project: {proj_name} ---")
# Submit the combined setup and analysis function to the pool
executor.submit(initial_project_setup_and_analysis, proj_name)
# We exit the 'with' block here, but the threads continue running in the background.
# The executor automatically manages the threads. We don't call shutdown(wait=True).
# Start the background thread for periodic checks
print("Starting background repository check thread...")
repo_check_thread = threading.Thread(target=periodic_repo_check, daemon=True)
# Start the background thread for PERIODIC checks (now uses its own thread pool internally)
print("--- Starting background periodic check manager thread ---")
# This thread now manages submitting tasks to its own pool
repo_check_thread = threading.Thread(target=periodic_repo_check, daemon=True, name="PeriodicCheckManager")
repo_check_thread.start()
# Run the Flask app
print("Starting Flask server on port 5050...")
# Use threaded=True for SSE background sending, debug=False for production/stability
# Run the Flask app - This will start *before* initial checks might be complete
print(f"--- Starting Flask server on http://0.0.0.0:5050 ... ---")
# Ensure Flask runs threaded to handle multiple requests (like SSE connections)
app.run(host='0.0.0.0', port=5050, debug=False, threaded=True)

30
config.py Normal file
View File

@ -0,0 +1,30 @@
import os
# --- Configuration ---
REPO_URL = "http://192.168.5.191:3000/LCI/MTN6" # Assuming same base URL for now? Needs clarification if varies per project.
BRANCH = "main" # Assuming same branch for all projects
# Define the root directory containing all project subdirectories
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# PROJECTS_ROOT_DIR is one level up from SCRIPT_DIR if config.py is at the root
# If config.py is inside a 'src' folder, adjust accordingly. Assuming root for now.
PROJECTS_ROOT_DIR = os.path.join(SCRIPT_DIR, 'projects')
# Relative path within a project's repo to the SCADA views
# Remove project-specific prefix, utils.py will find the correct parent folder
VIEWS_DIR_RELATIVE = "com.inductiveautomation.perspective/views/Detailed-Views"
# Relative path within a project's base folder for extracted drawing text
# We will construct this as 'project_name/extracted_texts' within PROJECTS_ROOT_DIR
TEXT_OUTPUT_FOLDER_RELATIVE = "extracted_texts"
CHECK_INTERVAL_SECONDS = 60
# --- Column Names from CSV (Adjust if necessary) ---
CSV_ALIAS_COL = 'Alias'
CSV_PANEL_COL = 'Control Panel'
CSV_EQ_TYPE_COL = 'Equipment Type' # Optional, for details modal
CSV_CONV_TYPE_COL = 'Type of Conveyor' # Optional, for details modal
# --- Ensure Projects Root Directory Exists ---
os.makedirs(PROJECTS_ROOT_DIR, exist_ok=True)

117
drawing_checker.py Normal file
View File

@ -0,0 +1,117 @@
import os
# Assume utils contains the necessary helper functions
import utils
from utils import normalize, get_text_output_dir_path
def check_drawings(project_name, manifest_data):
"""
Checks if aliases from manifest exist ANYWHERE within the combined text extracted
from all available drawings for a project.
Attempts to extract text from PDFs if the corresponding TXT file is missing.
Updates the 'found_drawing' flag in the manifest_data items directly.
"""
if not manifest_data:
print(f"[{project_name}] Drawings Check: No manifest data provided.")
return
print(f"[{project_name}] Starting Drawings check...")
text_output_dir = utils.get_text_output_dir_path(project_name)
pdf_source_dir = utils.get_pdf_dir_path(project_name)
os.makedirs(text_output_dir, exist_ok=True) # Ensure output dir exists
# --- Preliminary Step: Ensure TXT exists for every PDF ---
print(f"[{project_name}] Checking PDF source directory ({pdf_source_dir}) against text output directory ({text_output_dir})...")
extraction_attempts = 0
successful_extractions = 0
failed_extractions = 0
if not os.path.isdir(pdf_source_dir):
print(f" Warning: PDF source directory not found at '{pdf_source_dir}'. Skipping extraction check.")
else:
for pdf_filename in os.listdir(pdf_source_dir):
if pdf_filename.lower().endswith('.pdf'):
pdf_path = os.path.join(pdf_source_dir, pdf_filename)
txt_filename = os.path.splitext(pdf_filename)[0] + '.txt'
txt_path = os.path.join(text_output_dir, txt_filename)
if not os.path.exists(txt_path):
extraction_attempts += 1
print(f" TXT file '{txt_filename}' missing for PDF '{pdf_filename}'. Attempting extraction...")
try:
success = utils.extract_text_from_pdf(pdf_path, txt_path)
if success:
# print(f" Successfully extracted text to '{txt_filename}'.")
successful_extractions += 1
else:
# print(f" Extraction failed or produced no text for '{pdf_filename}'.")
failed_extractions += 1
except AttributeError:
print(f" ERROR: utils.extract_text_from_pdf function not found! Cannot extract text.")
break # Stop trying if function missing
except Exception as extract_err:
print(f" Error during text extraction for '{pdf_filename}': {extract_err}")
failed_extractions += 1
print(f"[{project_name}] Text extraction check complete. Attempted: {extraction_attempts}, Succeeded: {successful_extractions}, Failed: {failed_extractions}.")
# --- End Preliminary Step ---
# --- Main Check: Scan all available TXT files and compare aliases ---
print(f"[{project_name}] Reading and combining text from all .txt files in: {text_output_dir}...")
all_raw_content = "" # Combine all raw text content here
processed_files = 0
found_txt_files = []
try:
if not os.path.isdir(text_output_dir):
print(f" Error: Text output directory not found: {text_output_dir}. Cannot perform drawing check.")
for item in manifest_data: item['found_drawing'] = False
return
txt_files = [f for f in os.listdir(text_output_dir) if f.lower().endswith('.txt')]
if not txt_files:
print(" Warning: No .txt files found in the directory. Cannot perform drawing check.")
for item in manifest_data: item['found_drawing'] = False
return
for filename in txt_files:
filepath = os.path.join(text_output_dir, filename)
processed_files += 1
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Simple concatenation is sufficient now
all_raw_content += content + "\n" # Add newline as separator
found_txt_files.append(filename)
except Exception as e:
print(f" Warning: Could not read or process text file {filepath}: {e}")
print(f" Read content from {len(found_txt_files)} out of {processed_files} total .txt files found.")
# Step 2: Normalize the entire combined content ONCE
print(f" Normalizing combined text content...")
all_normalized_content = utils.normalize(all_raw_content)
print(f" Normalization complete. Total normalized length: {len(all_normalized_content)} chars.")
# Step 3: Check each manifest alias against the normalized combined content
found_count = 0
checked_count = 0
for item in manifest_data:
# Ensure 'found_drawing' is initialized to False
item['found_drawing'] = False
alias = item.get('alias') # Use lowercase 'alias' key
if alias:
checked_count += 1
normalized_alias = utils.normalize(alias)
if normalized_alias and normalized_alias in all_normalized_content:
item['found_drawing'] = True
found_count += 1
# else: item['found_drawing'] remains False
print(f"[{project_name}] Drawings check finished. Checked {checked_count} aliases. Found {found_count} aliases within the combined drawing text.")
except Exception as e:
print(f" Error during drawings check main phase: {e}")
# Ensure flags are false on error
for item in manifest_data: item['found_drawing'] = False

60
manifest_reader.py Normal file
View File

@ -0,0 +1,60 @@
import csv
import config
from utils import normalize, find_csv_path
def read_manifest(project_name):
"""Reads the manifest CSV for a specific project into a list of dictionaries."""
csv_filepath = find_csv_path(project_name)
if not csv_filepath:
return None
print(f"[{project_name}] Reading manifest: {csv_filepath}")
manifest_items = []
# Only require Alias and Panel now for basic grouping
required_cols = {config.CSV_ALIAS_COL, config.CSV_PANEL_COL}
optional_cols = {config.CSV_EQ_TYPE_COL, config.CSV_CONV_TYPE_COL}
try:
# Revert back to 'utf-8-sig' to handle potential BOM from Excel
with open(csv_filepath, mode='r', newline='', encoding='utf-8-sig') as infile:
reader = csv.DictReader(infile)
# Strip whitespace from headers for reliable matching
headers = set(h.strip() for h in reader.fieldnames if h) # Added 'if h' to handle potential empty headers
# Check for required columns
missing_required = required_cols - headers
if missing_required:
print(f"Error: Missing required columns in CSV '{csv_filepath}': {', '.join(missing_required)}")
print(f"Available columns: {', '.join(headers)}")
return None
for row in reader:
# Strip whitespace from values as well
alias = row.get(config.CSV_ALIAS_COL, "").strip()
panel = row.get(config.CSV_PANEL_COL, "").strip()
# Add if Alias and Control Panel are present (Panel needed for grouping results later)
if alias and panel:
item = {
"alias": alias,
"normalized_alias": normalize(alias),
"control_panel": panel,
# Add optional data if columns exist and have values
"equipment_type": row.get(config.CSV_EQ_TYPE_COL, "").strip() if config.CSV_EQ_TYPE_COL in headers else "N/A",
"conveyor_type": row.get(config.CSV_CONV_TYPE_COL, "").strip() if config.CSV_CONV_TYPE_COL in headers else "N/A",
# Status fields to be filled later
"found_scada": False,
"found_drawing": False
}
manifest_items.append(item)
elif alias and not panel:
print(f"Warning: Alias '{alias}' found in CSV but is missing its '{config.CSV_PANEL_COL}'. Skipping.")
# Add other specific warnings if needed
except FileNotFoundError:
print(f"Error: Manifest file not found at {csv_filepath}")
return None
except Exception as e:
print(f"Error reading CSV file {csv_filepath}: {e}")
return None
print(f"Read {len(manifest_items)} valid items from manifest.")
return manifest_items

View File

@ -5,6 +5,7 @@ import os
from pypdf import PdfReader
import sys
import re # Import the regex module
import argparse # Import argparse
def normalize(text):
"""Normalize string for comparison: lowercase, treat '-' and '_' the same, remove all whitespace."""
@ -159,35 +160,21 @@ def find_missing_aliases(aliases, pdf_text):
return sorted(list(missing)) # Return sorted list for consistent output
if __name__ == "__main__":
# --- Configuration ---
script_dir = os.path.dirname(os.path.abspath(__file__))
default_manifest_path = os.path.join(script_dir, 'MTN6 Equipment Manifest REV6(Conveyor List).csv')
default_pdf_folder = os.path.join(script_dir, 'pdfs')
default_text_output_folder = os.path.join(script_dir, 'extracted_texts')
# --- Argument Parsing ---
parser = argparse.ArgumentParser(description='Check if aliases from a manifest CSV exist in text extracted from PDFs.')
parser.add_argument('manifest_file', help='Path to the manifest CSV file.')
parser.add_argument('pdf_folder', help='Path to the folder containing PDF files.')
parser.add_argument('text_output_folder', help='Path to the folder where extracted text files should be saved/read from.')
args = parser.parse_args()
manifest_file_path = default_manifest_path
pdf_folder_path = default_pdf_folder
text_output_folder_path = default_text_output_folder
manifest_file_path = args.manifest_file
pdf_folder_path = args.pdf_folder
text_output_folder_path = args.text_output_folder
if len(sys.argv) == 3:
manifest_file_path = sys.argv[1]
pdf_folder_path = sys.argv[2]
# If args are provided, still save text relative to script/default location,
# or you could add a third argument for the text output path.
print("Using command-line paths for Manifest and PDFs.")
print(f" Manifest: {manifest_file_path}")
print(f" PDF Folder: {pdf_folder_path}")
print(f" Text Output Folder: {text_output_folder_path} (Default)")
elif len(sys.argv) != 1: # Check if incorrect number of args were given (but not zero extra)
print("Usage: python pdf_manifest_checker.py [<path_to_manifest.csv> <path_to_pdf_folder>]")
print("If no arguments are provided, default paths will be used.")
sys.exit(1) # Exit if incorrect arguments
else:
# No arguments provided, use defaults
print("Using default paths:")
print(f" Manifest: {manifest_file_path}")
print(f" PDF Folder: {pdf_folder_path}")
print(f" Text Output Folder: {text_output_folder_path}")
print("Using provided paths:")
print(f" Manifest: {manifest_file_path}")
print(f" PDF Folder: {pdf_folder_path}")
print(f" Text Output Folder: {text_output_folder_path}")
# ---------------------
aliases_from_manifest = read_aliases_from_manifest(manifest_file_path)

72
progress_calculator.py Normal file
View File

@ -0,0 +1,72 @@
def calculate_combined_progress(project_name, manifest_data):
"""Calculates the combined progress based on scada/drawing status for a project."""
print(f"[{project_name}] Calculating combined progress statistics...")
results = {
"overall": {
"total_csv": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0,
"percentage_found_both": 0,
"missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []
},
"panels": {}
}
if not manifest_data:
print("Warning: No manifest data to calculate progress from.")
return results # Return the empty structure
results["overall"]["total_csv"] = len(manifest_data)
for item in manifest_data:
panel = item.get('control_panel', 'Unknown Panel') # Use get with default
# Initialize panel data if not present
if panel not in results["panels"]:
results["panels"][panel] = {
"total": 0, "found_both": 0, "found_scada_only": 0, "found_drawing_only": 0, "missing_both": 0,
"percentage_found_both": 0,
"missing_list": [], "found_scada_only_list": [], "found_drawing_only_list": [], "found_both_list": []
}
results["panels"][panel]["total"] += 1
# Categorize and add to lists
# Create a copy of the item detail to avoid modifying the original if needed later
# Exclude 'normalized_alias' from the output dictionary
item_detail = {k: v for k, v in item.items() if k != 'normalized_alias'}
found_scada = item.get('found_scada', False)
found_drawing = item.get('found_drawing', False)
if found_scada and found_drawing:
category = "found_both"
elif found_scada and not found_drawing:
category = "found_scada_only"
elif not found_scada and found_drawing:
category = "found_drawing_only"
else: # Missing both
category = "missing_both"
results["overall"][category] += 1
results["panels"][panel][category] += 1
# Correct the list keys used for appending
if category == "missing_both":
results["overall"]["missing_list"].append(item_detail)
results["panels"][panel]["missing_list"].append(item_detail)
else:
results["overall"][f"{category}_list"].append(item_detail)
results["panels"][panel][f"{category}_list"].append(item_detail)
# Calculate percentages
if results["overall"]["total_csv"] > 0:
results["overall"]["percentage_found_both"] = round(
(results["overall"]["found_both"] / results["overall"]["total_csv"]) * 100, 1
)
for panel_data in results["panels"].values():
if panel_data["total"] > 0:
panel_data["percentage_found_both"] = round(
(panel_data["found_both"] / panel_data["total"]) * 100, 1
)
print("Combined progress calculation finished.")
# print(json.dumps(results, indent=2)) # DEBUG: Print structure if needed
return results

View File

@ -0,0 +1,806 @@
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
PRS1_2A_VFD13HP120 FPMPRS1_2B_VFD13HP120 FPM
PRS1_3_VFD17.5HP120 FPM
PRS1_4_VFD13HP120 FPMPRS1_6_VFD12HP120 FPMPRS2_1_VFD17.5HP120 FPM
PRS1_2A_JPE1
EPCPRS1_6_EPC1
EPCPRS1_6_EPC2
EPCPRS2_1_EPC1
EPCPRS2_1_EPC2GPRS2_1_S1
GPRS2_1_S2BBR
ABBWPRS1_2A_JR2
ABBWPRS1_3_JR1
ABBWPRS1_4_JR1GPRS1_6_S1BBR
GPRS1_6_S2BBR
11'-3" EL
9'-1" EL
ULC1_3_VFD12HP80FPMULC2_3_VFD12HP80FPMULC3_3_VFD12HP80FPMULC4_3_VFD12HP80FPM
PS8_1_VFD17.5HP140-200 FPM
PS8_2_VFD110HP180-240 FPM
PS8_4_VFD115HP240 FPM PS8_5_VFD115HP240 FPM
PS9_2_VFD17.5HP240 FPM
PS8_8_VFD17.5HP220-330 FPM
PS8_9_VFD115HP240 FPM
PS8_11_VFD110HP240 FPM
PS8_11_DIV1_LS1LSPS8_11_DIV1_LS2LSSOLPS8_11_DIV1_SOL1SOLPS8_11_DIV1_SOL2
PS8_11_DIV2_LS1LSPS8_11_DIV2_LS2LSSOLPS8_11_DIV2_SOL1SOLPS8_11_DIV2_SOL2
PS8_5_JPE1
PS8_11_JPE1
PS8_2_JPE1
PS8_1_JPE6
PS8_11_FIO1
WPS8_11_JR1ABB
WPS8_11_JR2
WPS8_9_JR1ABBPS8_5_SIO1
ABB
WPS8_1_JR2ABB RABB EPCULC1_3_EPC1
EPCULC1_3_EPC2
EPCULC2_3_EPC1
EPCULC3_3_EPC1
EPCULC4_3_EPC1
EPCPS8_2_EPC1
EPCPS8_1_EPC1
EPCPS8_11_EPC1
GPS8_11_S1ABBR
EPCPS8_5_EPC2ABBR
GPS8_5_S2
ABBR
EPCPS8_4_EPC1
EPCPS8_4_EPC2
BB
BBR
ARGPS8_4_S1
GPS8_4_S2 EPCPS8_5_EPC1GPS8_5_S1ABBR
PS8_1_FIO1PS8_1_FIO2
BB
PS8_12CH_FPE1
GBBAB
EPCPS8_2_EPC2
ULC1_3_JPE1
B
EPCPS9_2_EPC1
GPS9_2_S1
PS8-12CH-PS1PS
PS8_11_CH1_JPE1PS8_11_CH1_FPE1GPS8_11_CH1_S1
VV
FL_DPM3MCM05
FL_DPM4MCM05
ULGLA_DPM1MCM05
PRS2_DPM1MCM05
CH_DPM7MCM05 CH_DPM8MCM05 CH_DPM10MCM05 CH_DPM11MCM05
R
GPS8_2_S2
GPS8_2_S1BBR
PS8_4_SIO1 PS8_6_FIO1
PS8_6_VFD15HP240 FPM
PS8_8_TPE1PS8_8_TPE2 PS8_9_SIO1PS8_9_JPE1
PS8_11_TPE1
PS8_11_CH1_FPE2PS8_11_JPE2PS8_11_CH2_JPE1PS8_11_CH2_FPE1PS8_11_CH2_FPE2
GPS8_11_CH2_S1GBBAB PS8_11_FIO2
PS8_11_DIV3_LS1LSPS8_11_DIV3_LS2LSSOLPS8_11_DIV3_SOL1SOLPS8_11_DIV3_SOL2
PS8_11_DIV4_LS1LSPS8_11_DIV4_LS2LSSOLPS8_11_DIV4_SOL1SOLPS8_11_DIV4_SOL2
PS8_11_FIO3GBBABPS8_11_CH3_JPE1PS8_11_CH3_FPE1GPS8_11_CH3_S1
PS8_11_CH3_FPE2PS8_11_JPE4PS8_11_CH4_JPE1PS8_11_CH4_FPE1PS8_11_CH4_FPE2
GPS8_11_CH4_S1GBBAB PS8_11_FIO4
PS8_11_JPE3
ABPS8_11_JPE5PS8_11_FIO5
EPCPS9_2_EPC2BBRGPS9_2_S2PS9_1CH_JPE1
PS9_2_TPE1
PS9_3_VFD17.5HP190-270 FPM
WPS9_3_JR1ABB
PS9_3_FIO1 EPCPS9_3_EPC1ABBRB PS9-3-PS1PS
GPS9_3_S1
PS9_3_JPE1
GBBAB
GBBAB
GPS9_3CH2_S1
GPS9_3CH1_S1
PS9_3CH2_JPE1PS9_3CH2_FPE1PS9_3CH2_FPE2
PS9_3CH1_JPE1PS9_3CH1_FPE1PS9_3CH1_FPE2
GBBAB
GBBAB
GPS9_3CH4_S1
GPS9_3CH6_S1
PS9_3CH4_JPE1PS9_3CH4_FPE1PS9_3CH4_FPE2PS9_3CH3_JPE1PS9_3CH6_FPE1PS9_3CH6_FPE2
PS9_3DIV1_LS1LSPS9_3DIV1_LS2LSSOLPS9_3DIV1_SOL1SOLPS9_3DIV1_SOL2
PS9_3DIV2_LS1LSPS9_3DIV2_LS2LSSOLPS9_3DIV2_SOL1SOLPS9_3DIV2_SOL2
PS9_3DIV3_LS1LSPS9_3DIV3_LS2LSSOLPS9_3DIV3_SOL1SOLPS9_3DIV3_SOL2
PS9_3DIV4_LS1LSPS9_3DIV4_LS2LSSOLPS9_3DIV4_SOL1SOLPS9_3DIV4_SOL2
PS9_3_FIO2PS9_3_JPE3
PS9_3_FIO3PS9_3_JPE4
PS9_3_FIO4
PS9_3_JPE5PS9_4CH_FPE1
RGULC1_3_SS1
ULC1_3_ENC1
PS8_1_ENC1
PS8_2_ENC1
PS8_4_ENC1 PS8_5_ENC1 PS8_8_ENC1 PS8_9_ENC1
PS8_11_ENC1PS9_2_ENC1
ULC1_3_JPE2
RGULC1_3_SS2ULC2_3_JPE1ULC3_3_JPE1ULC4_3_JPE1
ULC2_3_ENC1ULC3_3_ENC1ULC4_3_ENC1RABBULC2_3_JPE2RABBULC3_3_JPE2RABBULC4_3_JPE2
WULC1_3_JR1
WULC2_3_JR1
WULC3_3_JR1
WULC4_3_JR1
RGULC2_3_SS1RGULC3_3_SS1RGULC4_3_SS1
PS8_1_FIO3PS8_1_FIO4PS8_1_FIO5
RGPS8_1_SS1
PS8_1_JPE2PS8_1_JPE1PS8_1_JPE3PS8_1_JPE4PS8_1_JPE5
WPS8_1_JR1ABB
PRS1_1ACH_JPE1
WPRS1_2A_JR1ABBB
PRS1_2A_FIO1
ABBRPRS2_1_JPE1
PRS1_2A_JPE2
PRS1_1BCH_JPE1ABBBPRS1_2B_JPE1
WPRS1_2B_JR1PRS1_2B_FIO1PRS1_2B_JPE2ABBWPRS1_2B_JR2PRS1_3_FIO1
PRS1_3_JPE1
PRS1_4_FIO1
PRS1_3_JPE2
PRS1_4_JPE1PRS1_4_JPE2PRS1_5CH_JPE1PRS1_5CH_FPE1PRS1_6_TPE1
PRS1_6_TPE2
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASHTRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
ERSCPRS1-7-ERSC1
MDR POWER SUPPLYPRS1-7-PS180AMDR POWER SUPPLYPRS1-7-PS280A
PRS1_7_PE1PRS1_7_PE2PRS1_7_PE3PRS1_7_PE4PRS1_7_PE5PRS1_7_PE6PRS1_7_PE7PRS1_7_PE8PRS1_7_PE9PRS1_7_PE10PRS1_7_PE11PRS1_7_PE12PRS1_7_PE13PRS1_7_PE14PRS1_7_PE15PRS1_7_PE16PRS1_7_PE17PRS1_7_PE18PRS1_7_PE19PRS1_7_PE20PRS1_7_PE21PRS1_7_PE22PRS1_7_PE23PRS1_7_PE24PRS1_7_PE25PRS1_7_PE26PRS1_7_PE27
ERSCPRS1-7-ERSC2
ERSCPRS1-7-ERSC3
ERSCPRS1-7-ERSC4
ERSCPRS1-7-ERSC5
ERSCPRS1-7-ERSC6
ERSCPRS1-7-ERSC7
ERSCPRS1-7-ERSC8
ERSCPRS1-7-ERSC9
ERSCPRS1-7-ERSC10
ERSCPRS1-7-ERSC11
ERSCPRS1-7-ERSC12
ERSCPRS1-7-ERSC13
ERSCPRS1-7-ERSC14
GDTC_SCH1_EN1
GDTC_SCH2_EN1BBGABBBGB
FL2074_2_PE1ABBB
FL2074_1CH_PE1
FL2074_2_PE2
FL2074_2_VFD15HP350FPM
FL2074_3CH_PE1BBBWFL2074_2_JR2
WFL2074_2_JR1
FL2078_2_PE1ABBB
FL2078_1CH_PE1
FL2078_2_PE2
FL2078_2_VFD15HP350FPM
FL2078_3CH_PE1BBBWFL2078_2_JR2
WFL2078_2_JR1
FL2086_2_PE1ABBB
FL2086_1CH_PE1
FL2086_2_PE2
FL2086_2_VFD15HP350FPM
FL2086_3CH_PE1BBBWFL2086_2_JR2
WFL2086_2_JR1
FL2090_2_PE1ABBB
FL2090_1CH_PE1
FL2090_2_PE2
FL2090_2_VFD15HP350FPM
FL2090_3CH_PE1BBBWFL2090_2_JR2
WFL2090_2_JR1
FL2094_2_PE1ABBB
FL2094_1CH_PE1
FL2094_2_PE2
FL2094_2_VFD15HP350FPM
FL2094_3CH_PE1BBBWFL2094_2_JR2
WFL2094_2_JR1
FL4082_2_PE1ABBB
FL4082_1CH_PE1
FL4082_2_PE2
FL4082_2_VFD15HP350FPM
FL4082_3CH_PE1BBBWFL4082_2_JR2
WFL4082_2_JR1
FL4078_2_PE1ABBB
FL4078_1CH_PE1
FL4078_2_PE2
FL4078_2_VFD15HP350FPM
FL4078_3CH_PE1BBBWFL4078_2_JR2
WFL4078_2_JR1
FL4074_2_PE1ABBB
FL4074_1CH_PE1
FL4074_2_PE2
FL4074_2_VFD15HP350FPM
FL4074_3CH_PE1BBBWFL4074_2_JR2
WFL4074_2_JR1
FL4070_2_PE1ABBB
FL4070_1CH_PE1
FL4070_2_PE2
FL4070_2_VFD15HP350FPM
FL4070_3CH_PE1BBBWFL4070_2_JR2
WFL4070_2_JR1
FL4066_2_PE1ABBB
FL4066_1CH_PE1
FL4066_2_PE2
FL4066_2_VFD15HP350FPM
FL4066_3CH_PE1BBBWFL4066_2_JR2
WFL4066_2_JR1
CH_DPM9MCM05 CH_DPM12MCM05CH_DPM7_FIOM2CH_DPM7_FIOM1CH_DPM7_FIOM4CH_DPM7_FIOM3CH_DPM7_FIOM6CH_DPM7_FIOM5CH_DPM7_FIOM8CH_DPM7_FIOM7CH_DPM8_FIOM2CH_DPM8_FIOM1CH_DPM8_FIOM4CH_DPM8_FIOM3CH_DPM8_FIOM6CH_DPM8_FIOM5CH_DPM8_FIOM8CH_DPM8_FIOM7CH_DPM9_FIOM2CH_DPM9_FIOM1CH_DPM9_FIOM4CH_DPM9_FIOM3CH_DPM9_FIOM5CH_DPM9_FIOM6 CH_DPM10_FIOM2CH_DPM10_FIOM1CH_DPM10_FIOM4CH_DPM10_FIOM3CH_DPM10_FIOM6CH_DPM10_FIOM5CH_DPM10_FIOM8CH_DPM10_FIOM7CH_DPM11_FIOM2CH_DPM11_FIOM1CH_DPM11_FIOM4CH_DPM11_FIOM3CH_DPM11_FIOM6CH_DPM11_FIOM5CH_DPM11_FIOM8CH_DPM11_FIOM7CH_DPM12_FIOM2CH_DPM12_FIOM1CH_DPM12_FIOM4CH_DPM12_FIOM3
GDTC_SCH3_EN1BBGB
GDTC_SCH4_EN1BBGB
GDTC_SCH5_EN1BBGAB
GDTC_SCH6_EN1BBGB
GDTC_SCH7_EN1BBGB
GDTC_SCH8_EN1BBGB
GDTC_SCH10_EN1BBGB
GDTC_SCH11_EN1BBGB
GDTC_SCH12_EN1BBGB
GDTC_SCH14_EN1BBGB
GDTC_SCH15_EN1BBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GBBGB
GDTC_SCH41_EN1BBGB
GDTC_SCH42_EN1BBGB
GDTC_SCH45_EN1BBGB
GDTC_SCH48_EN1BBGB
GDTC_SCH50_EN1BBGB
GDTC_SCH51_EN1BBGB
GDTC_SCH52_EN1BBGB
GDTC_SCH54_EN1BBGB
GDTC_SCH55_EN1BBGB
GDTC_SCH57_EN1BBGB
GDTC_SCH58_EN1BBGB
GDTC_SCH60_EN1BBGB
GDTC_SCH61_EN1BBGB
GDTC_SCH63_EN1BBGB
GDTC_SCH64_EN1BBGB
GDTC_SCH65_EN1BBGB
GDTC_SCH67_EN1BBGB
GDTC_SCH68_EN1BBGB
GDTC_SCH69_EN1BBGB
GDTC_SCH9_EN1BBGAB
GDTC_SCH13_EN1BBGAB
GDTC_SCH16_EN1BBGAB BBGAB
G G G G GBBGAB BBGAB BBGAB BBGAB BBGAB
G
GDTC_SCH40_EN1BBGAB
GDTC_SCH43_EN1BBGAB
GDTC_SCH46_EN1BBGAB
GDTC_SCH49_EN1BBGAB
GDTC_SCH44_EN1BBGB
GDTC_SCH47_EN1BBGB
GDTC_SCH53_EN1BBGAB
GDTC_SCH56_EN1BBGAB
GDTC_SCH59_EN1BBGAB
GDTC_SCH62_EN1BBGAB
GDTC_SCH66_EN1BBGAB
PB_SCH1_FIOH1
PB_SCH1_PE1
PB_SCH1_PE2
BBGAB
W
PB_SCH1_PKGREL_PB1
SOLPB_SCH1_PKGREL_SOL1
BBGB
PB_SCH2_PE1
PB_SCH2_PE2
SOL
PB_SCH2_PKGREL_SOL1
WPB_SCH2_PKGREL_PB1
PB_SCH3_FIOH1PB_SCH5_FIOH1PB_SCH7_FIOH1PB_SCH9_FIOH1PB_SCH11_FIOH1PB_SCH13_FIOH1PB_SCH15_FIOH1PB_SCH17_FIOH1PB_SCH18_FIOH1PB_SCH20_FIOH1PB_SCH22_FIOH1PB_SCH24_FIOH1PB_SCH26_FIOH1PB_SCH28_FIOH1PB_SCH30_FIOH1PB_SCH32_FIOH1PB_SCH33_FIOH1PB_SCH35_FIOH1PB_SCH36_FIOH1PB_SCH38_FIOH1PB_SCH39_FIOH1PB_SCH40_FIOH1PB_SCH41_FIOH1 PB_SCH42_FIOH1PB_SCH43_FIOH1PB_SCH44_FIOH1PB_SCH45_FIOH1PB_SCH46_FIOH1PB_SCH48_FIOH1PB_SCH50_FIOH1PB_SCH52_FIOH1PB_SCH54_FIOH1PB_SCH56_FIOH1PB_SCH58_FIOH1PB_SCH60_FIOH1PB_SCH62_FIOH1PB_SCH63_FIOH1PB_SCH65_FIOH1PB_SCH67_FIOH1PB_SCH69_FIOH1PB_SCH71_FIOH1PB_SCH73_FIOH1PB_SCH75_FIOH1
BBGB
PB_SCH3_PE1
PB_SCH3_PE2
SOLPB_SCH3_PKGREL_SOL1
W
PB_SCH3_PKGREL_PB1
BBGB
PB_SCH4_PE1
PB_SCH4_PE2
SOL
PB_SCH4_PKGREL_SOL1
WPB_SCH4_PKGREL_PB1
BBGB
PB_SCH6_PE1
PB_SCH6_PE2
SOL
PB_SCH6_PKGREL_SOL1
W
PB_SCH6_PKGREL_PB1
BBGB
PB_SCH7_PE1
PB_SCH7_PE2
SOLPB_SCH7_PKGREL_SOL1
W
PB_SCH7_PKGREL_PB1
BBGB
PB_SCH8_PE1
PB_SCH8_PE2
SOLPB_SCH8_PKGREL_SOL1
W
PB_SCH8_PKGREL_PB1
BBGB
PB_SCH10_PE1
PB_SCH10_PE2
SOLPB_SCH10_PKGREL_SOL1
W
PB_SCH10_PKGREL_PB1
BBGB
PB_SCH11_PE1
PB_SCH11_PE2
SOLPB_SCH11_PKGREL_SOL1
WPB_SCH11_PKGREL_PB1
BBGB
PB_SCH12_PE1
PB_SCH12_PE2
SOLPB_SCH12_PKGREL_SOL1
W
PB_SCH12_PKGREL_PB1
BBGB
PB_SCH14_PE1
PB_SCH14_PE2
SOLPB_SCH14_PKGREL_SOL1
W
PB_SCH14_PKGREL_PB1
BBGB
PB_SCH15_PE1
PB_SCH15_PE2
SOL
PB_SCH15_PKGREL_SOL1
WPB_SCH15_PKGREL_PB1
BBGB
PB_SCH16_PE1
PB_SCH16_PE2
SOLPB_SCH16_PKGREL_SOL1
W
PB_SCH16_PKGREL_PB1
BBGB
PB_SCH18_PE1
PB_SCH18_PE2
SOLPB_SCH18_PKGREL_SOL1
W
PB_SCH18_PKGREL_PB1
BBGB
PB_SCH19_PE1
PB_SCH19_PE2
SOLPB_SCH19_PKGREL_SOL1
W
PB_SCH19_PKGREL_PB1
BBGB
PB_SCH21_PE1
PB_SCH21_PE2
SOLPB_SCH21_PKGREL_SOL1
W
PB_SCH21_PKGREL_PB1
BBGB
PB_SCH22_PE1
PB_SCH22_PE2
SOLPB_SCH22_PKGREL_SOL1
WPB_SCH22_PKGREL_PB1
BBGB
PB_SCH23_PE1
PB_SCH23_PE2
SOLPB_SCH23_PKGREL_SOL1
W
PB_SCH23_PKGREL_PB1
BBGB
PB_SCH25_PE1
PB_SCH25_PE2
SOLPB_SCH25_PKGREL_SOL1
W
PB_SCH25_PKGREL_PB1
BBGB
PB_SCH26_PE1
PB_SCH26_PE2
SOLPB_SCH26_PKGREL_SOL1
W
PB_SCH26_PKGREL_PB1
BBGB
PB_SCH27_PE1
PB_SCH27_PE2
SOLPB_SCH27_PKGREL_SOL1
W
PB_SCH27_PKGREL_PB1
BBGB
PB_SCH29_PE1
PB_SCH29_PE2
SOLPB_SCH29_PKGREL_SOL1
WPB_SCH29_PKGREL_PB1
BBGB
PB_SCH30_PE1
PB_SCH30_PE2
SOLPB_SCH30_PKGREL_SOL1
W
PB_SCH30_PKGREL_PB1
BBGB
PB_SCH31_PE1
PB_SCH31_PE2
SOLPB_SCH31_PKGREL_SOL1
WPB_SCH31_PKGREL_PB1
BBGB
PB_SCH33_PE1
PB_SCH33_PE2
SOLPB_SCH33_PKGREL_SOL1
W
PB_SCH33_PKGREL_PB1
BBGB
PB_SCH34_PE1
PB_SCH34_PE2
SOLPB_SCH34_PKGREL_SOL1
W
PB_SCH34_PKGREL_PB1
BBGB
PB_SCH36_PE1
PB_SCH36_PE2
SOL
PB_SCH36_PKGREL_SOL1
WPB_SCH36_PKGREL_PB1
BBGB
PB_SCH37_PE1
PB_SCH37_PE2
SOLPB_SCH37_PKGREL_SOL1
W
PB_SCH37_PKGREL_PB1
BBGB
PB_SCH39_PE1
PB_SCH39_PE2
SOLPB_SCH39_PKGREL_SOL1
W
PB_SCH39_PKGREL_PB1
BBGB
PB_SCH41_PE1
PB_SCH41_PE2
SOLPB_SCH41_PKGREL_SOL1
W
PB_SCH41_PKGREL_PB1
BBGB
PB_SCH43_PE1
PB_SCH43_PE2
SOLPB_SCH43_PKGREL_SOL1
W
PB_SCH43_PKGREL_PB1
BBGB
PB_SCH45_PE1
PB_SCH45_PE2
SOLPB_SCH45_PKGREL_SOL1
W
PB_SCH45_PKGREL_PB1
BBGB
PB_SCH47_PE1
PB_SCH47_PE2
SOL
PB_SCH47_PKGREL_SOL1
WPB_SCH47_PKGREL_PB1
BBGB
PB_SCH49_PE1
PB_SCH49_PE2
SOL
PB_SCH49_PKGREL_SOL1
WPB_SCH49_PKGREL_PB1
BBGB
PB_SCH50_PE1
PB_SCH50_PE2
SOLPB_SCH50_PKGREL_SOL1
W
PB_SCH50_PKGREL_PB1
BBGB
PB_SCH51_PE1
PB_SCH51_PE2
SOL
PB_SCH51_PKGREL_SOL1
W
PB_SCH51_PKGREL_PB1
BBGB
PB_SCH53_PE1
PB_SCH53_PE2
SOLPB_SCH53_PKGREL_SOL1
W
PB_SCH53_PKGREL_PB1
BBGB
PB_SCH54_PE1
PB_SCH54_PE2
SOLPB_SCH54_PKGREL_SOL1
W
PB_SCH54_PKGREL_PB1
BBGB
PB_SCH55_PE1
PB_SCH55_PE2
SOLPB_SCH55_PKGREL_SOL1
W
PB_SCH55_PKGREL_PB1
BBGB
PB_SCH57_PE1
PB_SCH57_PE2
SOLPB_SCH57_PKGREL_SOL1
W
PB_SCH57_PKGREL_PB1
BBGB
PB_SCH58_PE1
PB_SCH58_PE2
SOLPB_SCH58_PKGREL_SOL1
WPB_SCH58_PKGREL_PB1
BBGB
PB_SCH59_PE1
PB_SCH59_PE2
SOLPB_SCH59_PKGREL_SOL1
W
PB_SCH59_PKGREL_PB1
BBGB
PB_SCH61_PE1
PB_SCH61_PE2
SOLPB_SCH61_PKGREL_SOL1
W
PB_SCH61_PKGREL_PB1
BBGB
PB_SCH62_PE1
PB_SCH62_PE2
SOL
PB_SCH62_PKGREL_SOL1
WPB_SCH62_PKGREL_PB1
BBGB
PB_SCH64_PE1
PB_SCH64_PE2
SOLPB_SCH64_PKGREL_SOL1
W
PB_SCH64_PKGREL_PB1
BBGB
PB_SCH65_PE1
PB_SCH65_PE2
SOL
PB_SCH65_PKGREL_SOL1
WPB_SCH65_PKGREL_PB1
BBGB
PB_SCH66_PE1
PB_SCH66_PE2
SOLPB_SCH66_PKGREL_SOL1
W
PB_SCH66_PKGREL_PB1
BBGB
PB_SCH68_PE1
PB_SCH68_PE2
SOLPB_SCH68_PKGREL_SOL1
W
PB_SCH68_PKGREL_PB1
BBGB
PB_SCH69_PE1
PB_SCH69_PE2
SOL
PB_SCH69_PKGREL_SOL1
W
PB_SCH69_PKGREL_PB1
BBGB
PB_SCH70_PE1
PB_SCH70_PE2
SOLPB_SCH70_PKGREL_SOL1
W
PB_SCH70_PKGREL_PB1
BBGB
PB_SCH72_PE1
PB_SCH72_PE2
SOLPB_SCH72_PKGREL_SOL1
W
PB_SCH72_PKGREL_PB1
BBGB
PB_SCH73_PE1
PB_SCH73_PE2
SOLPB_SCH73_PKGREL_SOL1
W
PB_SCH73_PKGREL_PB1
BBGB
PB_SCH75_PE1
PB_SCH75_PE2
SOLPB_SCH75_PKGREL_SOL1
W
PB_SCH75_PKGREL_PB1
PB_SCH5_PE1
PB_SCH5_PE2
BBGAB
W
PB_SCH5_PKGREL_PB1
SOLPB_SCH5_PKGREL_SOL1
PB_SCH9_PE1
PB_SCH9_PE2
BBGAB
WPB_SCH9_PKGREL_PB1
SOLPB_SCH9_PKGREL_SOL1
PB_SCH13_PE1
PB_SCH13_PE2
BBGAB
WPB_SCH13_PKGREL_PB1
SOLPB_SCH13_PKGREL_SOL1
PB_SCH17_PE1
PB_SCH17_PE2
BBGAB
WPB_SCH17_PKGREL_PB1
SOL
PB_SCH17_PKGREL_SOL1
PB_SCH20_PE1
PB_SCH20_PE2
BBGAB
WPB_SCH20_PKGREL_PB1
SOLPB_SCH20_PKGREL_SOL1
PB_SCH24_PE1
PB_SCH24_PE2
BBGAB
WPB_SCH24_PKGREL_PB1
SOL
PB_SCH24_PKGREL_SOL1
PB_SCH28_PE1
PB_SCH28_PE2
BBGAB
W
PB_SCH28_PKGREL_PB1
SOLPB_SCH28_PKGREL_SOL1
PB_SCH32_PE1
PB_SCH32_PE2
BBGAB
W
PB_SCH32_PKGREL_PB1
SOLPB_SCH32_PKGREL_SOL1
PB_SCH35_PE1
PB_SCH35_PE2
BBGAB
W
PB_SCH35_PKGREL_PB1
SOL
PB_SCH35_PKGREL_SOL1
PB_SCH38_PE1
PB_SCH38_PE2
BBGAB
WPB_SCH38_PKGREL_PB1
SOL
PB_SCH38_PKGREL_SOL1
PB_SCH40_PE1
PB_SCH40_PE2
BBGAB
W
PB_SCH40_PKGREL_PB1
SOLPB_SCH40_PKGREL_SOL1
PB_SCH42_PE1
PB_SCH42_PE2
BBGAB
WPB_SCH42_PKGREL_PB1
SOLPB_SCH42_PKGREL_SOL1
PB_SCH44_PE1
PB_SCH44_PE2
BBGAB
W
PB_SCH44_PKGREL_PB1
SOLPB_SCH44_PKGREL_SOL1
PB_SCH46_PE1
PB_SCH46_PE2
BBGAB
W
PB_SCH46_PKGREL_PB1
SOLPB_SCH46_PKGREL_SOL1
PB_SCH48_PE1
PB_SCH48_PE2
BBGAB
W
PB_SCH48_PKGREL_PB1
SOLPB_SCH48_PKGREL_SOL1
PB_SCH52_PE1
PB_SCH52_PE2
BBGAB
W
PB_SCH52_PKGREL_PB1
SOLPB_SCH52_PKGREL_SOL1
PB_SCH56_PE1
PB_SCH56_PE2
BBGAB
WPB_SCH56_PKGREL_PB1
SOLPB_SCH56_PKGREL_SOL1
PB_SCH60_PE1
PB_SCH60_PE2
BBGAB
WPB_SCH60_PKGREL_PB1
SOL
PB_SCH60_PKGREL_SOL1
PB_SCH63_PE1
PB_SCH63_PE2
BBGAB
WPB_SCH63_PKGREL_PB1
SOL
PB_SCH63_PKGREL_SOL1
PB_SCH67_PE1
PB_SCH67_PE2
BBGAB
WPB_SCH67_PKGREL_PB1
SOL
PB_SCH67_PKGREL_SOL1
PB_SCH71_PE1
PB_SCH71_PE2
BBGAB
W
PB_SCH71_PKGREL_PB1
SOLPB_SCH71_PKGREL_SOL1
PB_SCH74_PE1
PB_SCH74_PE2
BBGAB
WPB_SCH74_PKGREL_PB1
SOLPB_SCH74_PKGREL_SOL1
MCM05
MCM05

View File

@ -0,0 +1,387 @@
ST 4.0kW 1.4375
CL
ST 1.5kW 1.4375
12'-11"
12'-11"
S02-101CH2S02-101CH4S02-101CH6S02-101CH8S02-101CH10S02-101CH12S02-101CH14S02-101CH16S02-101CH18S02-101CH20S02-101CH22S02-101CH24S02-101CH26S02-101CH28S02-101CH30
S02-101CH1S02-101CH3S02-101CH5S02-101CH7S02-101CH9S02-101CH11S02-101CH13S02-101CH15S02-101CH17S02-101CH19S02-101CH21S02-101CH23S02-101CH25S02-101CH27S02-101CH29
NCS2-050-CH2NCS2-050-CH4NCS2-050-CH6NCS2-050-CH8NCS2-050-CH10NCS2-050-CH12NCS2-050-CH14NCS2-050-CH16NCS2-050-CH18NCS2-050-CH20NCS2-050-CH22NCS2-050-CH24NCS2-050-CH26NCS2-050-CH28NCS2-050-CH30
NCS2-050-CH1NCS2-050-CH3NCS2-050-CH5NCS2-050-CH7NCS2-050-CH9NCS2-050-CH11NCS2-050-CH13NCS2-050-CH15NCS2-050-CH17NCS2-050-CH19NCS2-050-CH21NCS2-050-CH23NCS2-050-CH25NCS2-050-CH27NCS2-050-CH29
NCP1_9_VFD12HP350 FPM
NCP1_10B_VFD12HP350 FPMNCP1_10A_VFD12HP350 FPM
NCP1_11_VFD12HP350 FPMNCP1_12_VFD12HP350 FPMNCP1_13_VFD12HP350 FPM
NCP1_14A_VFD12HP350 FPMNCP1_14B_VFD12HP350 FPMNCP1_14C_VFD12HP350 FPM
NCP1_15_VFD17.5HP350 FPM
NCP1_16_VFD15HP350 FPMNCP1_17_VFD12HP350 FPMNCP1_18A_VFD12HP350 FPM
NCP1_19_VFD12HP350 FPM
NCP1_20_VFD15HP300 FPM
NCS1_1_VFD12HP300 FPMNCS1_2_VFD12HP300 FPMNCS1_3_VFD15HP300 FPM
NCS1_4_VFD12HP300 FPM
NCS1_5B_VFD12HP300 FPMNCS1_5A_VFD12HP300 FPM
NCS1_6_VFD12HP240 FPMNCS1_7_VFD12HP240 FPMNCS1_8_VFD12HP240 FPMNCS1_9_VFD12HP240 FPMS02_1_VFD130HP250 FPM
NCP1_21_VFD17.5HP300 FPM
NCP2_1_VFD15HP300 FPMNCS2_2_VFD12HP300 FPM
NCS2_3A_VFD12HP300 FPM
NCS2_4_VFD12HP240 FPMNCS2_5_VFD12HP240 FPMNCS2_6_VFD12HP240 FPMNCS2_7_VFD12HP240 FPM
WGS02_102CH_ER1
S02_102CH_PE1
S02_102CH_PE2
SNDRCV
S02_1_LRPE1
MCM06
SNDRCV
S02_1_LRPE2
MCM06
RBB
EPCNCP1_9_EPC2RBB
EPCNCP1_15_EPC1RBB
EPCNCP1_15_EPC2ABB
NCP1_20_PE1(INFEED PRODUCT SENSOR)
NCP1_20_PE3(OUTFEED PRODUCT SENSOR)
NCP1_20_PE2(IDLE DEBRIS SENSOR)
NCP1_20_PE4(DRIVE END DEBRIS/SPROCKET ENGAGEMENT SENSOR)
NCP1_20_APS1APSNCP1_20_SOL1SOLNCP1_20_SOL2SOL
EPCNCP1_21_EPC1RBB
RGNCP1_21_SS1
EPCNCP1_21_EPC2
RG NCP1_21_SS2RBB
EPCNCS1_4_EPC4RBB
EPCNCS1_4_EPC2RBB
WNCP1_16_JR1ABB
WNCS1_3_JR1ABB
EPCNCS2_2_EPC2RBB
EPCNCS2_2_EPC1RBBRGNCS2_2_SS1
BBBGBBBG
WS02_1_JR2
A
WS02_1_JR1
S02_1_JPE1S02_1CH_JPE1
HBBBA
EPCNCS1_4_EPC3
EPCNCS1_4_EPC1
WNCP1_16_JR2R
GNCP1_9_S1
GNCP1_9_S2
GNCP1_15_S1
GNCP1_15_S2
GNCS1_4_S1
GNCS1_4_S2EPCS02_1_EPC2
EPCS02_1_EPC1
NCP1_11_ENC1NCP1_12_ENC1NCP1-13-ENC1 NCP1_15_ENC1
NCP1_17_ENC1
NCP1_19_ENC1
NCS1_2_ENC1
NCS1_4_ENC1NCS1_6_ENC1NCS1_7_ENC1NCS1_8_ENC1NCS1_9_ENC1
NCS2_7_ENC1NCS2_6_ENC1NCS2_5_ENC1NCS2_4_ENC1NCS2_2_ENC1
NCP1_21_ENC1
NCP1_15_JPE1
NCP1_17_TPE1
NCS1_1_TPE1
NCS1_4_TPE1
NCS2_2_TPE1
NCP1_21_TPE1
NCP1_9_TPE1NCP_11_TPE1
V
ST 4.0kW 1.4375
CL
ST 1.5kW 1.4375
S02-101CH2S02-101CH4S02-101CH6S02-101CH8S02-101CH10S02-101CH12S02-101CH14S02-101CH16S02-101CH18S02-101CH20S02-101CH22S02-101CH24S02-101CH26S02-101CH28S02-101CH30
S02-101CH1S02-101CH3S02-101CH5S02-101CH7S02-101CH9S02-101CH11S02-101CH13S02-101CH15S02-101CH17S02-101CH19S02-101CH21S02-101CH23S02-101CH25S02-101CH27S02-101CH29
NCS2-050-CH2NCS2-050-CH4NCS2-050-CH6NCS2-050-CH8NCS2-050-CH10NCS2-050-CH12NCS2-050-CH14NCS2-050-CH16NCS2-050-CH18NCS2-050-CH20NCS2-050-CH22NCS2-050-CH24NCS2-050-CH26NCS2-050-CH28NCS2-050-CH30
NCS2-050-CH1NCS2-050-CH3NCS2-050-CH5NCS2-050-CH7NCS2-050-CH9NCS2-050-CH11NCS2-050-CH13NCS2-050-CH15NCS2-050-CH17NCS2-050-CH19NCS2-050-CH21NCS2-050-CH23NCS2-050-CH25NCS2-050-CH27NCS2-050-CH29
EPCNCP1_9_EPC1
RGNCS2_2_SS2
NCS2_DPM1MCM06
NCS1_DPM1MCM06
NCP_DPM3MCM06
NCP_DPM2MCM06NCP_11_TPE2NCP1_12_TPE1NCP1_13_TPE1NCP1_15_TPE1
NCP1_14D_VFD12HP350 FPM
NCP1_17_TPE2
NCP1_18B_VFD12HP350 FPMNCP1_19_TPE1
NCP1_19_TPE2
NCS1_2_JPE1
NCS1_4_TPE2
NCS1_6_TPE1NCS1_6_TPE2NCS1_7_TPE1NCS1_8_TPE1NCS1_9_TPE1S02_104CH_FIOH1
S02_1_FIO1S02_104CH_PE1
S02_104CH_PE2
S02_106CH_PE1
S02_106CH_PE2
S02_108CH_PE1
S02_108CH_PE2
S02_110CH_PE1
S02_110CH_PE2
S02_112CH_PE1
S02_112CH_PE2
S02_114CH_PE1
S02_114CH_PE2
S02_116CH_PE1
S02_116CH_PE2
S02_118CH_PE1
S02_118CH_PE2
S02_120CH_PE1
S02_120CH_PE2
S02_122CH_PE1
S02_122CH_PE2
S02_124CH_PE1
S02_124CH_PE2
S02_126CH_PE1
S02_126CH_PE2
S02_128CH_PE1
S02_128CH_PE2
S02_130CH_PE1
S02_130CH_PE2
S02_101CH_PE1
S02_101CH_PE2BBBGA
WGS02_101CH_ER1
S02_103CH_FIOH1S02_103CH_PE1
S02_103CH_PE2
S02_105CH_PE1
S02_105CH_PE2
S02_107CH_PE1
S02_107CH_PE2
S02_109CH_PE1
S02_109CH_PE2
S02_111CH_PE1
S02_111CH_PE2
S02_113CH_PE1
S02_113CH_PE2
S02_115CH_PE1
S02_115CH_PE2
S02_117CH_PE1
S02_117CH_PE2
S02_119CH_PE1
S02_119CH_PE2
S02_121CH_PE1
S02_121CH_PE2
S02_123CH_PE1
S02_123CH_PE2
S02_125CH_PE1
S02_125CH_PE2
S02_127CH_PE1
S02_127CH_PE2
S02_129CH_PE1
S02_129CH_PE2 BBBG
WGS02_103CH_ER1WGS02_105CH_ER1WGS02_107CH_ER1WGS02_109CH_ER1WGS02_111CH_ER1WGS02_113CH_ER1WGS02_115CH_ER1WGS02_117CH_ER1WGS02_119CH_ER1WGS02_121CH_ER1WGS02_123CH_ER1WGS02_125CH_ER1WGS02_127CH_ER1WGS02_129CH_ER1
WGS02_104CH_ER1WGS02_106CH_ER1WGS02_108CH_ER1WGS02_110CH_ER1WGS02_112CH_ER1WGS02_114CH_ER1WGS02_116CH_ER1WGS02_118CH_ER1WGS02_120CH_ER1WGS02_122CH_ER1WGS02_124CH_ER1WGS02_126CH_ER1WGS02_128CH_ER1WGS02_130CH_ER1
SNDRCV
S02_1_LRPE3
MCM06
SNDRCV
S02_1_LRPE4
MCM06
WS02_1_JR4WS02_1_JR3
SNDRCV
S02_1_LRPE5
MCM06
SNDRCV
S02_1_LRPE6
MCM06
WS02_1_JR6WS02_1_JR5
SNDRCV
S02_1_LRPE7
MCM06
SNDRCV
S02_1_LRPE8
MCM06
WS02_1_JR8WS02_1_JR7
SNDRCV
S02_1_LRPE9
MCM06
SNDRCV
S02_1_LRPE10
MCM06
WS02_1_JR10WS02_1_JR9
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
S02_108CH_FIOH1
S02_107CH_FIOH1
S02_112CH_FIOH1
S02_111CH_FIOH1
S02_116CH_FIOH1
S02_115CH_FIOH1
S02_120CH_FIOH1
S02_119CH_FIOH1
S02_124CH_FIOH1
S02_123CH_FIOH1
S02_128CH_FIOH1
S02_127CH_FIOH1
S02_1_FIO2S02_1_FIO3S02_1_FIO4S02_1_FIO5S02_1_FIO6S02_1_FIO7S02_1_FIO8
S02_129CH_FIOH1
S02_2_VFD130HP250 FPM
WGS02_202CH_ER1
S02_202CH_PE1
S02_202CH_PE2
SNDRCV
S02_2_LRPE1
MCM06
SNDRCV
S02_2_LRPE2
MCM06
BBBGBBBG
WS02_2_JR2
A
WS02_2_JR1
S02_2_JPE1S02_2CH_JPE1
HBBBA
NCS2_050_PE1
EPCS02_2_EPC2
EPCS02_2_EPC1S02_204CH_FIOH1
S02_2_FIO1S02_204CH_PE1
S02_204CH_PE2
S02_206CH_PE1
S02_206CH_PE2
S02_208CH_PE1
S02_208CH_PE2
S02_210CH_PE1
S02_210CH_PE2
S02_212CH_PE1
S02_212CH_PE2
S02_214CH_PE1
S02_214CH_PE2
S02_216CH_PE1
S02_216CH_PE2
S02_218CH_PE1
S02_218CH_PE2
S02_220CH_PE1
S02_220CH_PE2
S02_222CH_PE1
S02_222CH_PE2
S02_224CH_PE1
S02_224CH_PE2
S02_226CH_PE1
S02_226CH_PE2
S02_228CH_PE1
S02_228CH_PE2
S02_230CH_PE1
S02_230CH_PE2
S02_201CH_PE1
S02_201CH_PE2BBBGA
WGS02_201CH_ER1
S02_203CH_FIOH1S02_203CH_PE1
S02_203CH_PE2
S02_205CH_PE1
S02_205CH_PE2
S02_207CH_PE1
S02_207CH_PE2
S02_209CH_PE1
S02_209CH_PE2
S02_211CH_PE1
S02_211CH_PE2
S02_213CH_PE1
S02_213CH_PE2
S02_215CH_PE1
S02_215CH_PE2
S02_217CH_PE1
S02_217CH_PE2
S02_219CH_PE1
S02_219CH_PE2
S02_221CH_PE1
S02_221CH_PE2
S02_223CH_PE1
S02_223CH_PE2
S02_225CH_PE1
S02_225CH_PE2
S02_227CH_PE1
S02_227CH_PE2
S02_229CH_PE1
S02_229CH_PE2 BBBG
WGS02_203CH_ER1WGS02_205CH_ER1WGS02_207CH_ER1WGS02_209CH_ER1WGS02_211CH_ER1WGS02_213CH_ER1WGS02_215CH_ER1WGS02_217CH_ER1WGS02_219CH_ER1WGS02_221CH_ER1WGS02_223CH_ER1WGS02_225CH_ER1WGS02_227CH_ER1WGS02_229CH_ER1
WGS02_204CH_ER1WGS02_206CH_ER1WGS02_208CH_ER1WGS02_210CH_ER1WGS02_212CH_ER1WGS02_214CH_ER1WGS02_216CH_ER1WGS02_218CH_ER1WGS02_220CH_ER1WGS02_222CH_ER1WGS02_224CH_ER1WGS02_226CH_ER1WGS02_228CH_ER1WGS02_230CH_ER1
SNDRCV
S02_2_LRPE3
MCM06
SNDRCV
S02_2_LRPE4
MCM06
WS02_2_JR4WS02_2_JR3
SNDRCV
S02_2_LRPE5
MCM06
SNDRCV
S02_2_LRPE6
MCM06
WS02_2_JR6WS02_2_JR5
SNDRCV
S02_2_LRPE7
MCM06
SNDRCV
S02_2_LRPE8
MCM06
WS02_2_JR8WS02_2_JR7
SNDRCV
S02_2_LRPE9
MCM06
SNDRCV
S02_2_LRPE10
MCM06
WS02_2_JR10WS02_2_JR9
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBGA
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
BBBG
S02_208CH_FIOH1
S02_207CH_FIOH1
S02_212CH_FIOH1
S02_211CH_FIOH1
S02_216CH_FIOH1
S02_215CH_FIOH1
S02_220CH_FIOH1
S02_219CH_FIOH1
S02_224CH_FIOH1
S02_223CH_FIOH1
S02_228CH_FIOH1
S02_227CH_FIOH1
S02_2_FIO2S02_2_FIO3S02_2_FIO4S02_2_FIO5S02_2_FIO6S02_2_FIO7S02_2_FIO8
S02_229CH_FIOH1
NCP1_21_JPE1
NCS2_2_TPE2
NCS2_3B_VFD12HP300 FPMNCS2_4_TPE1NCS2_4_TPE2NCS2_5_TPE1NCS2_6_TPE1
NCS2_7_TPE1
MCM06
MCM06

View File

@ -0,0 +1,449 @@
BYAB_2_VFD15HP550 FPM BYAB_3_VFD15HP550 FPM
BYAB_4_VFD13HP500 FPM
BYAB_5_VFD15HP450 FPMBYAB_6_VFD13HP400 FPM
BYAB_7_VFD17.5HP350 FPMBYAB_8_VFD15HP300 FPMBYAB_9_VFD12HP300 FPM
BYAB_10_VFD13HP300 FPM
BYAB_11_VFD13HP300 FPM
BYAB_12_VFD17.5HP300 FPMBYAB_13_VFD110HP300 FPMBYAB_14_VFD110HP300 FPMBYAB_15_VFD13HP300 FPM
BYAD_2_VFD13HP550 FPM BYAD_3_VFD17.5HP500 FPMBYAD_4_VFD15HP450 FPM
BYAD_5_VFD17.5HP400 FPM
BYAD_6_VFD13HP350 FPM BYAD_7_VFD110HP300 FPM
BYAD_8_VFD110HP300 FPMBYAD_9_VFD13HP300 FPM
BYAC_2_VFD13HP550 FPM BYAC_3_VFD17.5HP500 FPMBYAC_4_VFD15HP450 FPM
BYAC_5_VFD15HP400 FPM
BYAC_6_VFD13HP350 FPMBYAC_7_VFD110HP300 FPMBYAC_8_VFD110HP300 FPMBYAC_9_VFD13HP300 FPM
BYAC_10_VFD15HP300 FPMBYAC_11_VFD13HP300 FPM
ABB
BYAC_2_JPE1
SNDRCV
BYA_LRPE1
MCM07
BYAD_2_JPE1
BYAB_2_JPE1
BYAC_2_TPE1 BYAC_3_JPE1
BYAC_5_TPE1
BYAC_5_JPE1BYAC_7_TPE1BYAC_7_TPE2
BYAC_8_TPE1BYAC_9_TPE1
BYAC_11_TPE1
BYAD_3_JPE1
BYAB_2_JPE2
BYAD_2_TPE1
BYAB_6_TPE1BYAB_6_TPE2
BYAB_7_TPE1BYAB_8_JPE1
BYAB_12_TPE1BYAB_12_TPE2BYAB_13_TPE1
BYAB_14_TPE1BYAB_15_TPE1
BYAB_10_TPE1
BYAB_10_JPE1
BYAB_4_TPE1
BYAB_4_JPE1
BYAD_5_TPE1
BYAD_5_JPE1BYAD_7_TPE1 BYAD_7_TPE2
BYAD_8_TPE1BYAD_9_TPE1
EPCBYAB_15_EPC1RBB
EPCBYAB_15_EPC2RBB
R
G
BYAB_2_S1RBBGBYAB_2_S2
ABBR
ABBR
RBB
RBB
EPCBYAD_9_EPC1RBBGBYAD_9_S1
EPCBYAD_9_EPC2RBBGBYAD_9_S2
EPCBYAC_11_EPC1
ABBGBYAC_11_S2EPCBYAC_11_EPC2
RBBGBYAC_11_S1
GBYAC_2_S1
GBYAD_2_S2
GBYAC_2_S2
G
BYAD_2_S1
V
BYBD_2_VFD13HP550 FPM BYBD_3_VFD110HP550 FPM BYBD_4_VFD110HP450 FPM
BYBD_5_VFD110HP400 FPM BYBD_6_VFD15HP350 FPM BYBD_7_VFD17.5HP300 FPM
BYBD_8_VFD13HP300 FPM
BYBD_9_VFD12HP300 FPM
BYBD_10_VFD110HP300 FPM
BYBD_11_VFD13HP300 FPM
BYBD_12_VFD110HP300 FPM
BYBD_13_VFD110HP300 FPM
BYBD_14_VFD12HP300 FPM
BYBC_2_VFD13HP550 FPM BYBC_3_VFD110HP500 FPM BYBC_4_VFD17.5HP450 FPM
BYBC_5_VFD15HP400 FPM
BYBC_6_VFD13HP350 FPM
BYBC_7_VFD13HP300 FPM BYBC_8_VFD110HP300 FPM
BYBC_9_VFD110HP300 FPMBYBC_10_VFD12HP300 FPM
BYBA_2_VFD13HP550 FPM BYBA_4_VFD17.5HP450 FPM
BYBA_5_VFD13HP400 FPM
BYBA_9_VFD110HP300 FPM BYBA_10_VFD17.5HP300 FPMBYBA_11_VFD12HP300 FPMBYBA_12_VFD13HP300 FPM
BYBA_13_VFD12HP300 FPM
BYBA_14_VFD13HP300 FPMBYBA_15_VFD12HP300 FPM
BYBC_2_JPE1
BYBD_2_JPE1
BYBC_2_TPE1
BYBD_2_TPE1 BYBD_3_TPE1
BYBC_3_TPE1
BYBA_4_JPE1
BYBC_4_JPE1
BYBD_4_TPE1 BYBD_5_TPE1 BYBD_6_TPE1
BYBD_7_JPE1
BYBD_12_TPE1 BYBD_12_TPE2
BYBD_13_TPE1BYBD_14_TPE1
BYBA_10_TPE1
BYBC_9_TPE1BYBC_10_TPE1BYBC_8_TPE1 BYBC_8_TPE2
BYBA_10_JPE1BYBA_15_TPE1BYBA_15_TPE2
BYBA_13_TPE1
BYBC_6_JPE1
BYBC_6_TPE1
BYBD_9_TPE1
BYBD_10_JPE1 EPCBYBD_14_EPC1RBBGBYBD_14_S1
EPCBYBD_14_EPC2RBBGBYBD_14_S2
EPCBYBC_10_EPC1RBB
GBYBC_10_S1
EPCBYBC_10_EPC2RBBGBYBC_10_S2
EPCBYBA_15_EPC2
EPCBYBA_15_EPC1BB
GBYBA_15_S2RBB
BYCD_2_VFD15HP400 FPMBYCD_3_VFD15HP380 FPMBYCD_4_VFD12HP450 FPM
BYCD_5_VFD15HP400 FPM
BYCD_6_VFD13HP400 FPMBYCD_7_VFD12HP300 FPM
BYCD_8_VFD17.5HP300 FPM BYCD_9_VFD110HP300 FPM BYCD_10_VFD110HP300 FPM BYCD_11_VFD17.5HP300 FPMBYCD_12_VFD12HP300 FPM
BYCB_2_VFD13HP550 FPMBYCB_3_VFD15HP500 FPM
BYCB_4_VFD13HP450 FPM
BYCB_5_VFD15HP400 FPM
BYCB_6_VFD15HP350 FPMBYCB_7_VFD110HP300 FPMBYCB_8_VFD110HP300 FPM
BYCB_9_VFD15HP300 FPMBYCB_10_VFD12HP300 FPM
BYCA_2_VFD13HP550 FPMBYCA_3_VFD15HP500 FPM
BYCA_4_VFD15HP450 FPM
BYCA_5_VFD15HP400 FPM
BYCA_6_VFD110HP350 FPM
BYCA_7_VFD12HP300 FPMBYCA_8_VFD15HP300 FPMBYCA_9_VFD12HP300 FPM
BYCD_2_JPE1
SND RCVBYC_LRPE1MCM07
BYCB_2_TPE1BYCB_3_JPE1
BYCD_2_JPE2
BYCD_7_TPE1
BYCB_7_TPE1
BYCA_5_TPE1
BYCA_9_TPE2
EPCBYCD_12_EPC1RBBGBYCD_12_S1
EPCBYCD_12_EPC2RBBGBYCD_12_S2
EPCBYCB_10_EPC1RBBGBYCB_10_S1
EPCBYCB_10_EPC2RBBGBYCB_10_S2
EPCBYCA_9_EPC1RBBGBYCA_9_S1
EPCBYCA_9_EPC2RBBGBYCA_9_S2
BYDB_2_VFD13HP550 FPM
BYDB_3_VFD17.5HP500 FPMBYDB_4_VFD110HP450 FPMBYDB_5_VFD13HP400 FPMBYDB_6_VFD17.5HP350 FPMBYDB_7_VFD17.5HP300 FPMBYDB_8_VFD15HP300 FPMBYDB_9_VFD13HP300 FPMBYDB_10_VFD15HP300 FPM
BYDB_11_VFD15HP300 FPM
BYDB_12_VFD15HP300 FPMBYDB_13_VFD110HP300 FPMBYDB_14_VFD110HP300 FPMBYDB_15_VFD15HP300 FPMBYDB_16_VFD13HP300 FPM
BYDA_2_VFD13HP550 FPMBYDA_3_VFD17.5HP500 FPMBYDA_4_VFD110HP450 FPM
BYDA_5_VFD15HP400 FPM
BYDA_6_VFD110HP350 FPM
BYDA_7_VFD13HP300 FPMBYDA_8_VFD110HP300 FPMBYDA_9_VFD13HP300 FPM
BYDC_2_VFD13HP550 FPMBYDC_3_VFD17.5HP500 FPMBYDC_4_VFD110HP450 FPM
BYDC_5_VFD15HP400 FPM
BYDC_6_VFD110HP350 FPM
BYDC_7_VFD13HP300 FPM
BYDC_8_VFD110HP300 FPMBYDC_9_VFD110HP300 FPM
BYDC_10_VFD110HP300 FPMBYDC_11_VFD13HP300 FPM
BYDC_12_VFD15HP300 FPMBYDC_13_VFD13HP300 FPM
SND RCV
BYD_LRPE1
MCM07
WBYD_JR1BYDB_2_TPE1BYDB_11_TPE1
EPCBYDC_13_EPC1
ABBGBYDC_13_S2EPCBYDC_13_EPC2
RBBGBYDC_13_S1 EPCBYDA_9_EPC1RBBGBYDA_9_S1
EPCBYDA_9_EPC2RBBGBYDA_9_S2
EPCBYDB_16_EPC1RBBGBYDB_16_S1
EPCBYDB_16_EPC2RBBGBYDB_16_S2
EPCBYDA_8_EPC1
EPCBYDA_8_EPC2ABBGBYDA_8_S2
RBBGBYDA_8_S1
RR
R
BBA
WBYBA_5_JR1
BBA
WBYBC_5_JR1
BBA
WBYBC_7_JR1
R
BBA
WBYCB_6_JR1
BBA
WBYAB_11_JR1
BBA
WBYDB_12_JR1
BBA
WBYCB_4_JR1
BBA
WBYAB_9_JR1
BBA
WBYDB_10_JR1
BBA
WBYCD_6_JR1
BBA
WBYCD_3_JR1
BBA
WBYBD_11_JR1
BYBD_9_TPE2
BBA
WBYAD_6_JR1
BBA
WBYAD_4_JR1
BBA
WBYAC_4_JR1
EPCBYDA_6_EPC2
EPCBYDA_6_EPC1
EPCBYDB_4_EPC1
EPCBYDB_4_EPC2
EPCBYDC_6_EPC1
EPCBYDC_6_EPC2ABBGBYDA_6_S1R ABBGBYDC_6_S1
R
BBGBYDB_4_S2R
BBGBYDB_4_S1R
BBGBYDA_6_S2R BBGBYDC_6_S2R
GBYBA_15_S1
BBA
WBYCA_8_JR1
BBA
WBYBA_12_JR1
BBA
WBYBD_8_JR1
BBA
WBYAB_5_JR1
BBA
WBYAB_3_JR1
BYCD_4_TPE1
BYCB_5_JPE1
BYCB_5_TPE1
BYAC_11_TPE2
EPCBYAC_2_EPC1
EPCBYAC_2_EPC2EPCBYAD_2_EPC1
EPCBYAD_2_EPC2EPCBYAB_2_EPC2EPCBYAB_2_EPC1
EPCBYAB_2_EPC4EPCBYAB_2_EPC3
EPCBYDA_3_EPC1
EPCBYDA_3_EPC2EPCBYDB_3_EPC1
EPCBYDB_3_EPC2
ABBR
RBBGBYBC_3_S2
G
BYBC_3_S1EPCBYBC_3_EPC1
EPCBYBC_3_EPC2ABBR
RBBGBYBD_3_S2
G
BYBD_3_S1EPCBYBD_3_EPC1
EPCBYBD_3_EPC2
EPCBYCB_3_EPC1
EPCBYCB_3_EPC2ABBR
RBBGBYCB_3_S2
GBYCB_3_S1
BBR
GBYCD_5_S1RBBGBYCD_5_S2
ABBRGBYDA_3_S1
ABBR
GBYDB_3_S1RBBGBYDA_3_S2
RBBGBYDB_3_S2
2'-5" EL (PLTF2)TYP.
2'-5" EL (PLTF2)
11'-0" EL (PLTF1)9'-0" EL (PLTF1)19'-0'' EL (FLOOR)9'-0" EL (PLTF1)6'-8'' EL (PLTF2)2'-9" EL (PLTF2)
7'-6" EL (PLTF2)
7'-6" EL (PLTF2)2'-9" EL (PLTF2)
2'-9" EL (PLTF2)
X-SQ-43.1875_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-60-P2.5+
ST 1.5kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
TRANSITION CHUTES FROMCROSSBELT TO CONVEYORSURFACE WITH 3'' DROP
2'-5" EL (PLTF2)TYP.
7'-6" EL (PLTF2)
7'-6" EL (PLTF2)
7'-6" EL (PLTF2)
7'-6" EL (PLTF2)
7'-6" EL (PLTF2)
8'-8" EL (PLTF2)9'-0" EL (PLTF2)11'-0'' EL (PLTF1)
5'-1" EL (PLTF1)2'-9" EL (PLTF2)
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
2'-5" EL(PLTF2)
2'-5" EL (PLTF2)TYP. (3) PLCS
8'-0" EL (PLTF2)10'-4'' EL (PLTF1)TYP. (3) PLCS
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
8'-0" EL (PLTF2)
5'-8" EL (PLTF2)8'-0'' EL (PLTF1)
10'-8" EL (PLTF2) 10'-8" EL (PLTF2)
8'-0" EL (PLTF2)
8'-0" EL (PLTF2)
8'-0" EL (PLTF2) 8'-0" EL (PLTF2) 2'-9" EL (PLTF2)
8'-0" EL (PLTF2)
8'-0" EL (PLTF2)
8'-0" EL (PLTF2)
8'-0" EL (PLTF2) 8'-0" EL (PLTF2)2'-9" EL (PLTF2) 2'-9" EL (PLTF2)
2'-9" EL (PLTF2)
2'-9" EL (PLTF2)8'-0" EL (PLTF2) 8'-0" EL (PLTF2)
X-SQ-43.1875_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-60-P2.5+
ST 1.5kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
TRANSITION CHUTES FROMCROSSBELT TO CONVEYORSURFACE WITH 3'' DROP2'-5" EL(PLTF2)
7'-4" EL(PLTF2)
6'-3" EL(PLTF2) 10'-8" EL(PLTF2)
JSA
JSA
2'-5" EL(PLTF2)
X-SQ-43.1875_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-43.1875_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
2'-5" EL (PLTF2)TYP.
8'-0" EL (PLTF2)TYP.
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
3'-6" EL (PLTF3)
7'-5" EL (PLTF3)11'-11'' EL (PLTF2)
11'-11" EL (PLTF2)
8'-8" EL (PLTF2)8'-8" EL (PLTF2)11'-0'' EL (PLTF1)
11'-0" EL (PLTF1)
11'-0" EL (PLTF1)11'-0" EL (PLTF1)
2'-9" EL (PLTF2)5'-4'' EL (PLTF1)2'-9" EL (PLTF2)
3'-6" EL (PLTF3)
3'-6" EL (PLTF3)
3'-6" EL (PLTF3)
3'-6" EL (PLTF3)
3'-6" EL (PLTF3)8'-0'' EL (PLTF2)8'-0" EL (PLTF2)2'-9" EL (PLTF2)2'-9" EL (PLTF2) 3'-6" EL (PLTF3)8'-0'' EL (PLTF2)
2'-9" EL (PLTF2)
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
BBA
WBYAC_6_JR1
BYD1_DPM1MCM07
BYD1_DPM2MCM07
BYD3_DPM1MCM07
BYA1_DPM1MCM07
BYA3_DPM1MCM07
BYB1_DPM1MCM07
BYB2_DPM1MCM07
BYB3_DPM1MCM07
BYC1_DPM1MCM07
BYC3_DPM1MCM07
MCM07
BYAB_2_FIO1
BYAD_2_FIO1
BYAC_2_FIO1BYCA_2_FIO1
BYCB_2_FIO1
GBYAB_15_S2
GBYAB_15_S1BYAB_14_FIO1
BYAD_8_FIO1
BYAC_11_FIO1
BYBD_2_FIO1
BYBD_14_FIO1
BYBC_2_FIO1
BYBC_10_FIO1
BYBA_2_FIO1BYBA_2_JPE1BYBA_2_TPE1ABBR
RBBGBYBA_3_S2
G
BYBA_3_S1EPCBYBA_3_EPC1
EPCBYBA_3_EPC2 BYBA_3_VFD110HP500 FPMBYBA_3_TPE1
BYBA_6_TPE1
BYBA_6_VFD13HP350 FPM
BYBA_6_JPE1
BBA
WBYBA_7_JR1
BYBA_7_VFD13HP300 FPMBYBA_8_TPE1 BYBA_8_VFD110HP300 FPM
BYBA_8_TPE2 BYBA_9_TPE1
BYBA_13_JPE1BBA
WBYBA_14_JR1
BYBA_15_FIO1
EPCBYCD_5_EPC2
EPCBYCD_5_EPC1
BYCD_4_TPE2
BYCD_5_JPE1
BYCD_7_TPE2
BYCD_8_TPE1 BYCD_9_TPE1 BYCD_10_TPE1 BYCD_11_TPE1BYCD_12_TPE1BYCD_11_FIO1
BYCB_2_JPE1
BYCA_2_JPE1
BYCB_7_TPE2BYCB_8_TPE1BYCB_9_TPE1BYCB_10_TPE1
BYCB_9_FIO1
BYCA_2_TPE1
EPCBYCA_3_EPC1
EPCBYCA_3_EPC2ABBRRBB
GBYCA_3_S2
GBYCA_3_S1BYCA_3_JPE1
BBA
WBYCA_4_JR1 BYCA_5_TPE2 BYCA_6_TPE1BYCA_7_JPE1
BYCA_9_TPE1
SND RCV
BYB_LRPE1
MCM07
WBYB_JR1
WBYC_JR1WBYA_JR1
BYDB_2_FIO1
BYDA_2_FIO1
BYDC_2_FIO1BYDC_2_JPE1
BYDA_2_JPE1
BYDB_2_JPE1
BYDB_3_TPE1BYDB_4_TPE1BYDB_5_TPE1BYDB_6_TPE1BYDB_7_TPE1BYDB_8_TPE1BYDB_9_JPE1
BYDB_11_JPE1BYDB_13_TPE1BYDB_13_TPE2BYDB_14_TPE1
BYDB_15_TPE1BYDB_16_TPE1
BYDA_2_TPE1BYDA_3_TPE1BYDA_4_JPE1
BYDA_6_TPE1
BYDA_6_FIO1
BYDA_6_JPE1BYDA_8_TPE1BYDA_8_FIO1BYDA_9_TPE1BYDA_8_TPE2BYDA_8_FIO2
EPCBYDC_3_EPC1
EPCBYDC_3_EPC2ABBRGBYDC_3_S1RBBGBYDC_3_S2BYDC_2_TPE1BYDC_3_TPE1BYDC_4_JPE1
BYDC_6_FIO1
BYDC_6_TPE1
BYDC_6_JPE1
BYDC_8_FIO1
BYDC_8_TPE1
EPCBYDC_8_EPC1
EPCBYDC_8_EPC2ABB
GBYDC_8_S2
RBBGBYDC_8_S1RBYDC_8_TPE2BYDC_9_TPE1
BYDC_10_TPE1BYDC_11_JPE1BYDC_13_TPE1
BYDC_13_TPE2
MCM07

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,475 @@
SQR4010T
S7000
SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375ST 1.5kW 1.4375
PALLET STAGING
PALLET STAGING
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR40
10T
S7000
SQR4010T
S7000
SQR4010T
S7000
SQR4010T
S7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR40
10T
S7000
SQR4010T
S7000
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
SQR4010T
S7000
SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
TRAILER DIMENSIONS
TRAILER DIMENSIONS
EL. 2'-6"
EL. 2'-6"EL. 2'-6"
EL. 5'-2"TYP. (2) PLCS
EL. 6'-4"TYP. (2) PLCS
EL. 13'-1"EL. 3'-1" (PLTF)TYP. (4) PLCS
EL. 2'-6"
EL. 5'-2"TYP. (2) PLCS
EL. 6'-4"TYP. (2) PLCS
EL. 13'-1"EL. 3'-1" (PLTF)TYP. (3) PLCS
UL1_3_VFD15HP280 FPM UL1_4_VFD15HP280 FPMUL1_5_VFD13HP420 FPMUL1_6_VFD13HP420 FPMUL1_7_VFD13HP420 FPM
UL1_8_VFD13HP420 FPM UL1_9_VFD15HP280 FPM UL1_10A_VFD13HP280 FPMUL1_10B_VFD13HP280 FPM
UL1_11_VFD12HP280 FPMUL1_12_VFD12HP280 FPMUL1_13_VFD15HP280 FPM
UL2_3_VFD13HP280 FPM UL2_4_VFD15HP280 FPM
UL2_5_VFD13HP280 FPMUL2_6_VFD13HP420 FPMUL2_7_VFD13HP420 FPMUL2_8_VFD13HP420 FPMUL2_9_VFD13HP420 FPMUL2_10_VFD13HP420 FPM
UL3_1_VFD12HP120 FPM
UL3_2_VFD17.5HP280 FPMUL3_3_VFD13HP280 FPMUL3_4_VFD12HP280 FPMUL3_5_VFD13HP420 FPMUL3_6_VFD13HP420 FPMUL3_7_VFD13HP420 FPMUL3_8_VFD13HP420 FPMUL3_9_VFD13HP420 FPM
UL4_3_VFD13HP280 FPM UL4_4_VFD15HP280 FPM
UL4_9_VFD13HP280 FPM
UL5_3_VFD13HP280 FPM UL5_4_VFD15HP280 FPM
UL3_3_ENC1
UL3_5_ENC1UL3_6_ENC1UL3_7_ENC1UL3_8_ENC1UL2_9_ENC1UL2_8_ENC1UL2_7_ENC1UL2_6_ENC1
UL2_5_ENC1
UL1_5_ENC1UL1_6_ENC1UL1_7_ENC1
UL5_3_ENC1 UL5_4_ENC1
UL4_3_ENC1 UL4_4_ENC1
UL3_2_ENC1
UL3_4_ENC1UL2_3_ENC1 UL2_4_ENC1
UL1_3_ENC1 UL1_4_ENC1 UL1_9_ENC1
UL1_5_TPE1UL1_6_TPE1UL1_7_TPE1
UL1_9_TPE3
UL1_9_TPE1
UL1_9_TPE2
UL1_9_TPE4
UL1_9_TPE5
UL1_11_TPE1UL1_11_TPE2
UL1_12_TPE1
UL4_9_TPE1
UL4_9_TPE2
UL4_9_TPE3
UL4_9_TPE4
UL4_9_TPE5
UL5_3_TPE2UL4_3_TPE2
UL3_1_TPE1
UL2_3_TPE2UL1_3_TPE2
UL1_4_TPE1UL2_4_TPE1 UL5_4_TPE1
UL3_4_TPE1
UL3_4_TPE2UL3_5_TPE1UL3_6_TPE1UL3_7_TPE1UL3_8_TPE1UL2_9_TPE1UL2_8_TPE1
UL2_7_TPE1UL2_6_TPE2UL2_6_TPE1
EPCUL5_4_EPC2
EPCUL5_4_EPC1
EPCUL4_4_EPC2
EPCUL4_4_EPC1
EPCUL3_2_EPC2
EPCUL3_2_EPC1EPCUL2_4_EPC2
EPCUL2_4_EPC1
EPCUL1_4_EPC2
EPCUL1_4_EPC1
RBBGUL5_4_S2
RBBGUL5_4_S1RBBGUL4_4_S2
RBBGUL4_4_S1
RBBGUL3_2_S2
RBBGUL2_4_S2RBBGUL3_2_S1
RBBGUL2_4_S1RBBGUL1_4_S2
RBBGUL1_4_S1
EPCUL4_9_EPC2
EPCUL4_9_EPC1
RBBGUL4_9_S2
RBBGUL4_9_S1A
EPCUL1_9_EPC2
EPCUL1_9_EPC1
RBBGUL1_9_S2
RBBGUL1_9_S1A
RBB
RBBGPS1_1_S2
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
EPCUL3_1_EPC2EPCUL3_1_EPC1
RGUL3_1_SS1
WUL1_9_JR1
WUL1_9_JR2
WUL4_9_JR2
WUL4_9_JR1
UL7_3_VFD15HP280 FPMUL7_4_VFD15HP280 FPM
UL9_3_VFD15HP280 FPM
UL9_4_VFD15HP280 FPM
UL11_3_VFD15HP280 FPM
UL11_11A_VFD13HP280 FPMUL11_11B_VFD13HP280 FPMUL11_12_VFD12HP280 FPMUL11_13_VFD12HP280 FPM
UL11_14_VFD15HP280 FPM
UL12_3_VFD15HP280 FPM
UL7_3_TPE2
UL11_9_ENC1UL11_12_ENC1UL11_13_ENC1
UL7_4_ENC1UL7_3_ENC1
EPCUL11_9_EPC1
EPCUL11_10_EPC1
RBBGUL11_9_S1A
RBBGUL11_10_S1
BB
GPS3_1_S2
RBBGPS4_1_S2
RA
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
WUL11_9_JR1
V
PS2_3_VFD15HP300 FPM
PS1_1_VFD12HP300 FPMPS1_2_VFD12HP300 FPM
PS2_4_VFD13HP300 FPM PS2_5_VFD17.5HP300 FPMPS2_6_VFD13HP300 FPM
PS1_4_VFD17.5HP300 FPMPS1_3_VFD12HP300 FPM
PS1_5_VFD15HP300 FPM
EPCPS1_1_EP2EPCPS1_1_EPC1RBBGPS1_1_S1
EPCPS2_6_EPC2RBBGPS2_6_S2
EPCPS1_5_EPC1
RBBGPS1_5_S1
PS1_2_TPE1
PS1_4_TPE1
PS1_4_TPE2
PS2_5_TPE1
PS2_5_TPE2
BBA
PS1_2_TPE2 EPCPS1_5_EPC2
RBBGPS1_5_S2
STOP THE EPC AT 8'-3"
EPCPS2_6_EPC1PS2_6_TPE1PS1_5_TPE1
PS2_3_JPE1
RBBGPS2_6_S1
PS4_1_VFD12HP300 FPMPS4_2_VFD13HP300 FPMPS4_3_VFD12HP220 FPMPS4_4_VFD13HP220 FPMPS4_5_VFD15HP300 FPM PS4_6_VFD17.5HP300 FPM PS4_7_VFD17.5HP300 FPM PS4_8_VFD15HP300 FPM
PS3_1_VFD12HP300 FPMPS3_2_VFD13HP300 FPM
PS3_3_VFD13HP300 FPMPS3_4_VFD12HP300 FPM PS3_5_VFD15HP300 FPM PS3_6_VFD15HP300 FPM PS3_7_VFD110HP300 FPM
PS4_9_VFD13HP300 FPM
PS3_8_VFD13HP300 FPMPS3_9_VFD15HP300 FPMPS3_10_VFD15HP300 FPM
PS4_11_VFD15HP300 FPMPS4_12_VFD13HP300 FPM
PS3_11_VFD13HP300 FPMPS3_12_VFD13HP300 FPM
PS4_13_VFD13HP300 FPMPS4_14_VFD13HP300 FPM
EPCPS4_1_EPC2EPCPS4_1_EPC1RBBGPS4_1_S1
EPCPS3_1_EPC1EPCPS3_1_EPC2RBBGPS3_1_S1
EPCPS4_14_EPC1R
EPCPS3_12_EPC1RBBGPS3_12_S1
PS4_2_TPE1
PS3_1_TPE1
PS3_5_TPE1
PS3_5_TPE2 PS3_9_TPE1
PS4_14_TPE1
PS3_12_TPE1
BBAWPS3_3_JR1PS3_3_JPE1
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
EPCPS3_12_EPC2
RBBGPS3_12_S2A
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
EPCPS4_14_EPC2
BBA
BBA
BBGPS4_14_S1A
RBBGPS4_14_S2BBA
WPS4_8_JR1
WPS4_13_JR1
WPS3_7_JR1
WPS2_3_JR1RBB
STOP THE EPC AT 8'-3"
EPCUL2_3_EPC2
EPCUL2_3_EPC1
RBB
STOP THE EPC AT 8'-3"
EPCUL1_3_EPC2
EPCUL1_3_EPC1
ST 1.5kW 1.4375
ST 1.5kW 1.4375
STOP THE EPC AT 8'-3"
ST 1.5kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
ST 1.5kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-90-P2.5+
ST 4.0kW 1.4375
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
V
PS3_2_TPE1
EPCUL4_3_EPC2
EPCUL4_3_EPC1
RBBEPCUL5_3_EPC2
EPCUL5_3_EPC1
EPCUL7_1_EPC2
EPCUL7_1_EPC1
GUL2_3_S2
GUL1_3_S2
UL3_2_TPE2
GUL5_3_S2
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
UL1_3_TPE1UL2_3_TPE1
UL2_3_LPE1
UL2_3_LPE2UL4_3_TPE1
UL4_3_LPE1
UL4_3_LPE2UL5_3_TPE1
UL5_3_LPE2
UL5_3_LPE1
UL2_3_FIO1
UL1_3_FIO1
UL3_2_TPE1UL3_2_LPE2
UL3_2_LPE1
UL3_2_FIO1
UL4_3_FIO1
UL5_3_FIO1
UL6_2_FIO1
UL7_3_FIO1
UL8_2_FIO1
UL9_3_FIO1
UL10_2_FIO1
UL11_3_FIO1
UL12_3_FIO1
RBBGUL1_3_S1
RBBGUL2_3_S1
UL1_3_LPE1
UL1_3_DPE2
UL1_8_ENC1UL1_8_TPE1
RBB
RBBGUL4_3_S2
RBBGUL4_3_S1 UL4_5_VFD13HP420 FPMUL4_6_VFD13HP420 FPMUL4_7_VFD13HP420 FPM
UL4_8_VFD13HP420 FPM
UL4_5_ENC1UL4_6_ENC1UL4_7_ENC1UL4_5_TPE1UL4_6_TPE1UL4_7_TPE1
UL4_4_TPE1
UL4_8_ENC1UL4_8_TPE1 UL4_10A_VFD13HP280 FPMUL4_10B_VFD13HP280 FPM
UL4_11_VFD12HP280 FPMUL4_12_VFD12HP280 FPMUL4_13_VFD15HP280 FPM
UL4_11_TPE1UL4_11_TPE2
UL4_12_TPE1RBBGPS2_1_S2EPCPS2_1_EP2EPCPS2_1_EPC1RBBGPS2_1_S1PS2_2_TPE1PS2_1_VFD12HP300 FPMPS2_2_VFD12HP300 FPMPS2_2_TPE2
RBBGUL5_3_S1
UL5_5_VFD13HP280 FPMUL5_6_VFD13HP420 FPMUL5_7_VFD13HP420 FPMUL5_8_VFD13HP420 FPMUL5_9_VFD13HP420 FPMUL5_10_VFD13HP420 FPMUL5_9_ENC1UL5_8_ENC1UL5_7_ENC1UL5_6_ENC1
UL5_5_ENC1
UL5_9_TPE1UL5_8_TPE1
UL5_7_TPE1UL5_6_TPE2UL5_6_TPE1
UL6_1_VFD12HP120 FPM UL6_2_VFD17.5HP280 FPM
UL6_3_VFD13HP280 FPM
UL6_4_VFD12HP280 FPMUL6_5_VFD13HP420 FPMUL6_6_VFD13HP420 FPMUL6_7_VFD13HP420 FPMUL6_8_VFD13HP420 FPMUL6_9_VFD13HP420 FPM
UL6_3_ENC1
UL6_5_ENC1UL6_6_ENC1UL6_7_ENC1UL6_8_ENC1
UL6_2_ENC1
UL6_4_ENC1
UL6_1_TPE1 UL6_4_TPE1
UL6_4_TPE2UL6_5_TPE1UL6_6_TPE1UL6_7_TPE1UL6_8_TPE1
EPCUL6_2_EPC2
EPCUL6_2_EPC1
RBBGUL6_2_S2RBBGUL6_2_S1
RBBEPCUL6_1_EPC2EPCUL6_1_EPC1
RGUL6_1_SS1
UL6_2_TPE2
UL6_2_TPE1UL6_2_LPE2
UL6_2_LPE1RBB
UL12_DPM1MCM01
UL11_DPM1MCM01
UL10_DPM1MCM01
UL9_DPM1MCM01
UL8_DPM1MCM01
UL7_DPM1MCM01
UL6_DPM1MCM01
UL5_DPM1MCM01
UL4_DPM1MCM01
UL3_DPM1MCM01
UL2_DPM1MCM01
UL1_DPM1MCM01
PS1_DPM1MCM01
PS3_DPM1MCM01 PS3_DPM2MCM01
MCM01
RBBGUL7_3_S2UL7_3_TPE1
UL7_3_LPE2
UL7_3_LPE1RBBGUL7_3_S1 UL7_9_VFD13HP280 FPM
UL7_9_TPE2
UL7_9_TPE4
UL7_9_TPE5EPCUL7_4_EPC2
EPCUL7_4_EPC1
RBBGUL7_4_S2
RBBGUL7_4_S1
EPCUL7_9_EPC2
EPCUL7_9_EPC1
RBBGUL7_9_S2
RBBGUL7_9_S1A
WUL7_9_JR2
WUL7_9_JR1
UL7_5_VFD13HP420 FPMUL7_6_VFD13HP420 FPMUL7_7_VFD13HP420 FPM
UL7_8_VFD13HP420 FPM
UL7_5_ENC1UL7_6_ENC1UL7_7_ENC1UL7_5_TPE1UL7_6_TPE1UL7_7_TPE1
UL7_4_TPE1
UL7_8_ENC1UL7_8_TPE1 UL7_10A_VFD13HP280 FPMUL7_10B_VFD13HP280 FPM
UL7_11_VFD12HP280 FPMUL7_12_VFD12HP280 FPM
UL7_13_VFD15HP280 FPM
UL7_11_TPE1UL7_11_TPE2
UL7_12_TPE1
UL7_9_TPE1
PS3_6_TPE1
PS3_7_JPE1
PS3_9_TPE2
PS3_10_JPE1
PS3_12_TPE2
UL8_1_VFD12HP120 FPM
UL8_1_TPE1
RBBEPCUL8_1_EPC2EPCUL8_1_EPC1
RGUL8_1_SS1
UL8_2_TPE1UL8_2_LPE2
UL8_2_LPE1RBB
UL8_3_VFD15HP280 FPMUL8_3_ENC1
UL8_3_TPE1
EPCUL8_3_EPC2RBBGUL8_3_S2
RBBGUL8_3_S1
UL8_4_VFD13HP280 FPMUL8_5_VFD13HP420 FPMUL8_6_VFD13HP420 FPMUL8_7_VFD13HP420 FPMUL8_8_VFD13HP420 FPMUL8_9_VFD13HP420 FPMUL8_8_ENC1UL8_7_ENC1UL8_6_ENC1
UL8_5_ENC1
UL8_4_ENC1
UL8_8_TPE1UL8_7_TPE1
UL8_6_TPE1UL8_5_TPE2UL8_5_TPE1
UL8_2_VFD17.5HP120 FPM
EPCUL8_3_EPC1
EPCUL9_1_EPC2
EPCUL9_1_EPC1
RBBGUL9_3_S2UL9_3_TPE1
UL9_3_LPE2
UL9_3_LPE1RBBGUL9_3_S1 UL9_3_ENC1
UL9_5_VFD12HP280 FPM
UL9_6_VFD12HP280 FPMUL9_7_VFD13HP420 FPMUL9_8_VFD13HP420 FPMUL9_9_VFD13HP420 FPMUL9_10_VFD13HP420 FPMUL9_11_VFD12HP420 FPM
UL9_5_ENC1
UL9_7_ENC1UL9_8_ENC1UL9_9_ENC1UL9_10_ENC1
UL9_6_ENC1
UL9_6_TPE1
UL9_6_TPE2UL9_7_TPE1UL9_8_TPE1UL9_9_TPE1UL9_10_TPE1
EPCUL9_4_EPC2
EPCUL9_4_EPC1
RBBGUL9_4_S2RBBGUL9_4_S1UL9_4_ENC1
UL8_2_TPE2
UL9_3_TPE2
UL9_4_TPE1
UL10_1_VFD12HP120 FPM
UL10_1_TPE1
RBBEPCUL10_1_EPC2EPCUL10_1_EPC1
RGUL10_1_SS1
UL10_2_TPE1
UL10_2_LPE2
UL10_2_LPE1RBB
UL10_2_VFD17.5HP120 FPM EPCUL10_3_EPC2RBBGUL10_3_S2RBBGUL10_3_S1EPCUL10_3_EPC1
UL10_2_TPE2UL10_3_VFD12HP120 FPMUL10_4_VFD13HP420 FPMUL10_5_VFD13HP420 FPMUL10_6_VFD13HP420 FPMUL10_7_VFD13HP420 FPM
UL10_4_ENC1UL10_5_ENC1UL10_6_ENC1UL10_4_TPE1UL10_5_TPE1UL10_6_TPE1
UL10_3_TPE1
UL10_7_ENC1UL10_7_TPE1UL10_8_VFD13HP420 FPMUL10_9_VFD13HP420 FPM
UL10_10_VFD12HP420 FPM
UL10_8_ENC1
UL10_9_TPE1UL10_9_TPE2UL10_9_ENC1
EPCUL11_1_EPC2EPCUL11_1_EPC1
RBBG
UL11_3_S2
UL11_3_TPE1UL11_3_LPE2
UL11_3_LPE1RBBGUL11_3_S1 UL11_3_ENC1UL11_3_TPE2
EPCUL11_4_EPC2
EPCUL11_4_EPC1
RBBGUL11_4_S2RBBGUL11_4_S1
UL11_8_VFD13HP300 FPM
UL11_5_ENC1UL11_6_ENC1UL11_7_ENC1
UL11_5_TPE1UL11_6_TPE1UL11_7_TPE1UL11_4_TPE1
UL11_8_ENC1UL11_8_TPE1
UL11_7_VFD13HP300 FPMUL11_6_VFD13HP300 FPMUL11_5_VFD13HP300 FPMUL11_4_VFD15HP420 FPM
UL11_9_TPE1UL11_9_TPE2
UL11_9_TPE3UL11_9_VFD13HP300 FPMUL11_10_VFD15HP280 FPM
UL11_10_ENC1
UL11_10_TPE1
UL11_10_TPE2
UL11_12_TPE1
UL11_12_TPE2
UL11_13_TPE1
EPCUL12_3_EPC2
EPCUL12_3_EPC1
RBBG
UL12_3_S2UL12_3_TPE1UL12_3_LPE2
UL12_3_LPE1RBBGUL12_3_S1
UL12_3_TPE2UL12_3_ENC1
UL12_4_VFD15HP280 FPMUL12_4_ENC1
UL12_4_TPE1
EPCUL12_4_EPC2
EPCUL12_4_EPC1
RBBGUL12_4_S2
RBBGUL12_4_S1
UL12_5_VFD12HP280 FPMUL12_6_VFD13HP420 FPMUL12_8_VFD13HP420 FPM
UL12_9_VFD13HP420 FPMUL12_10_VFD12HP420 FPMUL12_9_ENC1UL12_8_ENC1UL12_7_ENC1UL12_6_ENC1
UL12_5_ENC1
UL12_9_TPE1UL12_8_TPE1UL12_7_TPE1UL12_6_TPE2UL12_6_TPE1UL12_7_VFD13HP420 FPM
UL11_14_TPE1
PS4_2_JPE1
PS4_4_TPE1PS4_4_TPE2
BBA
WPS4_2_JR1
PS4_5_TPE1 PS4_6_TPE1 PS4_7_TPE1 PS4_8_JPE1
PS4_10_TPE1
PS4_10_VFD110HP300 FPM
PS4_10_TPE2
PS4_11_TPE1
PS4_12_JPE1
MCM01

View File

@ -0,0 +1,401 @@
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR40
10T
S7000
SQR4010T
S7000
SQR4010T
S7000
SQR4010T
S7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR40
10T
S7000
SQR4010T
S7000
SQR4010TS7000
SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR40
10T
S7000
SQR4010TS7000
SQR4010T
S7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000 SQR4010TS7000
SQR4010TS7000
SQR40
10T
S7000
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
PS5_12_TPE2
UL16_3_TPE2 UL17_4_TPE1
UL16_2_TPE2UL16_3_TPE1UL15_4_TPE1
UL15_3_TPE2
UL14_3_TPE2
UL13_10_TPE2
UL13_11_TPE1
UL21_16_TPE1
UL21_15_TPE2
UL21_11_ENC1
UL18_10_ENC1
UL18_3_ENC1
UL17_3_ENC1 UL17_4_ENC1
UL16_2_ENC1
UL15_3_ENC1 UL15_4_ENC1
UL15_6_ENC1
UL13_8_ENC1
UL14_3_ENC1
UL21_13_TPE1
UL21_13_TPE2
UL13_8_TPE5
UL13_8_TPE4
UL13_8_TPE3
UL13_8_TPE2
UL13_8_TPE1
UL18_12_TPE2
PS5_8_JPE1
UL21_11_TPE2
UL21_11_TPE3
UL21_11_TPE4
UL16_4_TPE1
UL17_5_TPE1UL17_7_TPE1UL17_7_TPE2UL17_8_TPE1UL17_9_TPE1
UL16_6_TPE1
UL15_6_TPE1
UL18_10_TPE1
UL18_10_TPE2UL18_10_TPE3
UL18_10_TPE4PS5_10_TPE1
UL13_10_ENC1UL13_11_ENC1
UL18_14_ENC1UL18_15_ENC1
UL21_15_ENC1UL21_16_ENC1UL21_13_ENC1UL16_4_ENC1
UL17_5_ENC1UL17_6_ENC1
UL17_7_ENC1UL17_8_ENC1UL17_9_ENC1
UL16_5_ENC1 UL21_12_ENC1
UL15_11_ENC1
EPCUL17_4_EPC2
EPCUL17_4_EPC1
EPCUL16_2_EPC2
EPCUL16_2_EPC1
EPCUL15_4_EPC2
EPCUL15_4_EPC1
RBBGUL17_4_S2
RBBGUL17_4_S1RBBGUL16_2_S2
RBBGUL16_2_S1RBBGUL15_4_S2
RBBGUL15_4_S1
EPC
UL21_11_EPC1
EPCUL21_11_EPC2
RBBGUL21_11_S1A
RBBGUL21_11_S2
EPCUL21_13_EPC1RBBGUL21_13_S1
EPCUL21_13_EPC2RBBGUL21_13_S2
EPC
UL18_10_EPC1RBBGUL18_10_S1A
EPCUL18_10_EPC2RBBGUL18_10_S2
EPCUL13_8_EPC2
EPCUL13_8_EPC1
RBBGUL13_8_S2
RBBGUL13_8_S1A
BBGPS7_1_S2
BBGPS6_1_S2
BBGPS5_1_S2
RA
RA
RA
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
RBB
STOP THE EPC AT 8'-3"
WUL13_8_JR2
WUL13_8_JR1
WUL18_10_JR3
W
UL18_10_JR1
WUL21_11_JR1
V
PS5_1_VFD12HP300 FPMPS5_2_VFD13HP300 FPM
PS6_1_VFD12HP300 FPMPS6_2_VFD13HP300 FPM
PS7_1_VFD12HP300 FPMPS7_2_VFD13HP300 FPM
PS5_3_VFD13HP300 FPM
PS5_4_VFD13HP300 FPM
PS5_5_VFD15HP300 FPM PS5_6_VFD15HP300 FPMPS5_7_VFD110HP300 FPM
PS5_8_VFD15HP300 FPM
PS5_9_VFD13HP300 FPM
PS5_10_VFD110HP300 FPM
PS5_11_VFD12HP300 FPMPS5_12_VFD110HP300 FPMPS5_13_VFD13HP300 FPM
PS6_5_VFD15HP300 FPM
PS6_7_VFD17.5HP300 FPM
PS6_9_VFD13HP300 FPM
PS6_10_VFD110HP300 FPM
PS6_11_VFD13HP300 FPMPS6_12_VFD110HP300 FPMPS6_13_VFD13HP300 FPM
PS7_3_VFD12HP300 FPMPS7_4_VFD13HP300 FPMPS7_5_VFD17.5HP300 FPM
PS7_6_VFD17.5HP300 FPM
PS7_7_VFD110HP300 FPM PS7_8_VFD110HP300 FPM PS7_9_VFD17.5HP300 FPM PS7_10_VFD13HP300 FPM
PS7_14_VFD17.5HP300 FPM
PS5_4_TPE1PS5_4_TPE2 PS5_5_JPE1
PS5_7_TPE1
UL18_10_TPE5
PS5_10_JPE1PS5_12_TPE1
PS5_13_TPE1PS6_12_TPE2
PS6_8_JPE1
UL21_15_TPE1
PS6_4_TPE1
PS7_14_TPE2
EPCPS5_1_EPC1EPCPS5_1_EPC2
RBBGPS5_1_S1
EPCPS6_1_EPC1EPCPS6_1_EPC2
RBBGPS6_1_S1
EPCPS7_1_EPC1EPCPS7_1_EPC2
RBBGPS7_1_S1
EPCPS5_13_EPC1
EPCPS6_13_EPC2
EPCPS7_14_EPC1
RBB
PS5_2_JPE1 BBA
WPS5_6_JR1
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
BBA
WPS7_13_JR1 EPCPS7_14_EPC2
RBB
EPCPS7_9_EPC1
RBBGPS7_9_S2EPCPS7_9_EPC2
RBBGPS7_9_S1
WPS5_9_JR1
BBA
WPS6_8_JR1
BBA
WPS5_9_JR2
W
PS6_9_JR1
W
UL21_5_JR1
BBA
WPS6_11_JR1
EPCPS5_13_EPC2
EPCPS6_13_EPC1RBB
W
PS5_10_JR1
BBA
WPS5_11_JR1
RBB
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-41.5_43-180-P4.0
ST 4.0kW 1.4375
X-SQ-41.5_43-180-P4.0
ST 4.0kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
X-SQ-96_43-90-P2.5+
ST 4.0kW 1.4375
STOP THE EPC AT 8'-3"
STOP THE EPC AT 8'-3"
V
UL13_10_TPE1
UL13_2_FIO1
UL14_3_FIO1
UL15_3_FIO1
UL16_2_FIO1
UL17_3_FIO1
UL18_3_FIO1
UL19_2_FIO1
PS7_DPM3MCM02
PS5_DPM1MCM02
UL13_DPM1MCM02
UL14_DPM1MCM02
UL15_DPM1MCM02
PS6_DPM1MCM02
PS7_DPM1MCM02 PS7_DPM2MCM02
UL16_DPM1MCM02
UL18_DPM1MCM02
UL19_DPM1MCM02
UL20_DPM1MCM02
MCM02
UL17_DPM1MCM02
UL21_DPM1MCM02
UL13_1_VFD12HP120 FPM
UL13_1_TPE1
RBBEPCUL13_1_EPC2EPCUL13_1_EPC1
RGUL13_1_SS1
UL13_2_TPE1UL13_2_LPE2
UL13_2_LPE1RBB
EPCUL13_4_EPC2
EPCUL13_4_EPC1
RBBGUL13_4_S2
RBBGUL13_4_S1UL13_4_VFD13HP300 FPMUL13_5_VFD13HP300 FPMUL13_6_VFD3HP300 FPM
UL13_7_VFD13HP300 FPM
UL13_4_ENC1UL13_5_ENC1UL13_6_ENC1UL13_4_TPE1UL13_5_TPE1UL13_6_TPE1
UL13_3_TPE1
UL13_7_ENC1UL13_7_TPE1UL13_3_VFD13HP280 FPMUL13_2_VFD17.5HP280 FPM UL13_8_VFD15HP280 FPMUL13_9A_VFD13HP300 FPMUL13_9B_VFD13HP300 FPMUL13_10_VFD12HP280 FPMUL13_11_VFD12HP280 FPMUL13_12_VFD15HP280 FPM
PS5_2_TPE1
RGPS5_13_SS1
PS5_12_FIO1
EPCUL14_3_EPC2
EPCUL14_3_EPC1
UL14_3_TPE1
UL14_3_LPE1
UL14_3_LPE2RBBGUL14_3_S2
RBBGUL14_3_S1
RBB
UL14_3_VFD15HP280 FPM UL14_4_VFD15HP280 FPM
UL14_5_VFD13HP280 FPMUL14_6_VFD13HP420 FPMUL14_8_VFD13HP420 FPMUL14_9_VFD13HP420 FPMUL14_10_VFD13HP420 FPMUL14_9_ENC1UL14_8_ENC1UL14_7_ENC1UL14_6_ENC1UL14_4_ENC1
UL14_4_TPE1UL14_9_TPE1
UL14_8_TPE1
UL14_7_TPE2UL14_7_TPE1UL14_5_TPE1
EPCUL14_4_EPC2
EPCUL14_4_EPC1
RBBGUL14_4_S2
RBBGUL14_4_S1
UL14_5_ENC1
EPCUL15_3_EPC2
EPCUL15_3_EPC1
UL15_3_TPE1
UL15_3_LPE1
UL15_3_LPE2RBBGUL15_3_S2
RBBGUL15_3_S1UL15_3_VFD15HP280 FPM UL15_4_VFD15HP280 FPM
UL15_5_VFD12HP280 FPMUL15_5_ENC1
UL15_6_VFD13HP420 FPMUL15_7_VFD13HP420 FPMUL15_8_VFD13HP420 FPMUL15_9_VFD13HP420 FPMUL15_10_VFD13HP420 FPMUL15_11_VFD12HP420 FPM
UL15_6_TPE2UL15_7_ENC1UL15_7_TPE1UL15_8_TPE1UL15_9_TPE1
UL15_10_TPE1
UL15_8_ENC1UL15_9_ENC1UL15_10_ENC1
UL16_1_VFD12HP120 FPM
UL16_1_TPE1
RBBEPCUL16_1_EPC2EPCUL16_1_EPC1
RGUL16_1_SS1
UL16_2_TPE1UL16_2_LPE2
UL16_2_LPE1RBB UL16_2_VFD17.5HP280 FPMUL16_3_VFD13HP280 FPM
UL16_4_VFD13HP280 FPMUL16_5_VFD12HP280 FPM
UL16_6_ENC1UL16_7_ENC1UL16_8_ENC1UL16_6_TPE2UL16_7_TPE1UL16_8_TPE1UL16_6_VFD13HP280 FPMUL16_7_VFD13HP280 FPMUL16_8_VFD13HP280 FPMUL16_9_VFD12HP280 FPM
EPCUL17_3_EPC2
EPCUL17_3_EPC1
UL17_3_TPE1
UL17_3_LPE1
UL17_3_LPE2RBBGUL17_3_S2
RBBGUL17_3_S1UL17_3_VFD15HP120 FPM UL17_4_VFD15HP280 FPM
UL17_5_VFD13HP420 FPMUL17_6_VFD12HP280 FPMUL17_7_VFD13HP420 FPMUL17_8_VFD13HP420 FPM
UL17_10_VFD12HP420 FPMUL17_9_VFD13HP420 FPM
EPCUL18_3_EPC2
EPCUL18_3_EPC1
UL18_3_TPE1
UL18_3_LPE1
UL18_3_LPE2RBBGUL18_3_S2
RBBGUL18_3_S1UL18_3_VFD15HP120 FPM
UL18_3_TPE2 UL18_4_TPE1
UL18_4_ENC1
UL18_5_TPE1UL18_5_ENC1
EPCUL18_4_EPC2
EPCUL18_4_EPC1
RBBGUL18_4_S2
RBBGUL18_4_S1UL18_4_VFD15HP280 FPM
UL18_5_VFD13HP420 FPMUL18_6_VFD15HP280 FPMUL18_6_ENC1UL18_7_TPE1UL18_7_ENC1UL18_7_TPE2UL18_8_TPE1UL18_9_TPE1
UL18_8_ENC1UL18_9_ENC1
UL18_7_VFD13HP280 FPMUL18_8_VFD13HP280 FPMUL18_9_VFD13HP280 FPM
WUL18_10_JR2
UL18_10_VFD15HP280 FPM
UL18_12_TPE1
UL18_11_VFD15HP280 FPMUL18_12_VFD13HP280 FPM
UL18_12_ENC1
UL18_11_ENC1UL18_13A_VFD13HP280 FPMUL18_13B_VFD13HP280 FPM
UL18_14_TPE1
UL18_14_TPE2
UL18_15_TPE1
UL18_14_VFD12HP280 FPMUL18_15_VFD12HP280 FPMUL18_16_VFD15HP280 FPM
UL19_1_VFD12HP120 FPM
UL19_1_TPE1
RBBEPCUL19_1_EPC2EPCUL19_1_EPC1
RGUL19_1_SS1
UL19_2_TPE1UL19_2_LPE2
UL19_2_LPE1RBB
UL20_3_ENC1UL20_3_FIO1EPCUL20_3_EPC2
EPCUL20_3_EPC1
UL20_3_TPE1
UL20_3_LPE1
UL20_3_LPE2RBBGUL20_3_S2
RBBGUL20_3_S1UL20_3_VFD15HP120 FPM
UL20_3_TPE2
UL21_3_ENC1UL21_3_FIO1EPCUL21_3_EPC2
EPCUL21_3_EPC1
UL21_3_TPE1
UL21_3_LPE1
UL21_3_LPE2RBBGUL21_3_S2
RBBGUL21_3_S1UL21_3_VFD15HP120 FPM
UL21_3_TPE2
UL19_2_ENC1EPCUL19_2_EPC2
EPCUL19_2_EPC1
RBBGUL19_2_S2
RBBGUL19_2_S1UL19_2_VFD17.5HP280 FPMUL19_2_TPE2
UL19_3_VFD12HP280 FPMUL19_3_ENC1
UL19_4_TPE1
UL19_4_TPE2
UL19_4_ENC1UL19_4_VFD12HP280 FPMUL19_5_ENC1UL19_6_ENC1UL19_7_ENC1UL19_8_ENC1UL19_5_TPE1UL19_6_TPE1UL19_7_TPE1UL19_8_TPE1UL19_9_VFD12HP420 FPMUL19_8_VFD13HP420 FPM
UL19_7_VFD13HP420 FPMUL19_6_VFD13HP420 FPMUL19_5_VFD13HP420 FPM
UL20_4_ENC1EPCUL20_4_EPC2
EPCUL20_4_EPC1
RBBGUL20_4_S2
RBBGUL20_4_S1UL20_4_VFD15HP280 FPMUL20_44_TPE1
UL20_5_VFD12HP280 FPMUL20_5_ENC1
UL20_6_TPE1UL20_6_ENC1
UL20_11_VFD12HP420 FPMUL20_10_VFD13HP420 FPMUL20_9_VFD13HP420 FPM
UL20_7_VFD13HP420 FPMUL20_8_VFD13HP420 FPM
UL20_6_VFD13HP420 FPM
UL20_6_TPE2UL20_7_TPE1UL20_8_TPE1UL20_9_TPE1UL20_10_TPE1
UL20_7_ENC1UL20_8_ENC1UL20_9_ENC1UL20_10_ENC1
UL21_4_ENC1EPCUL21_4_EPC2
EPCUL21_4_EPC1
RBBGUL21_4_S2
RBBGUL21_4_S1UL21_4_VFD15HP280 FPMUL21_4_TPE1UL21_6_TPE1
UL21_5_VFD15HP280 FPM
UL21_5_ENC1UL21_6_ENC1UL21_7_ENC1UL21_8_ENC1UL21_9_ENC1UL21_10_ENC1
UL21_6_TPE2UL21_7_TPE1UL21_8_TPE1UL21_9_TPE1UL21_10_TPE1UL21_11_TPE1
UL21_6_VFD13HP300 FPMUL21_7_VFD13HP300 FPMUL21_8_VFD13HP300 FPMUL21_9_VFD13HP300 FPMUL21_10_VFD13HP300 FPM
WUL21_11_JR2
UL21_11_VFD17.5HP280 FPM
UL21_11_TPE5
UL21_12_VFD15HP280 FPM UL21_13_VFD12HP280 FPMUL21_14A_VFD13HP280 FPMUL21_14B_VFD13HP280 FPMUL21_15_VFD12HP280 FPM
UL21_16_VFD12HP280 FPM
UL21_17_VFD15HP280 FPMPS6_2_JPE1PS6_2_TPE1
PS6_3_VFD12HP300 FPM
PS6_4_TPE2
PS6_4_VFD13HP300 FPM PS6_6_VFD15HP300 FPM
PS6_5_JPE1
PS6_7_TPE1BBA
WPS6_6_JR1
PS6_8_VFD15HP300 FPMPS6_10_TPE1
PS6_10_JPE1PS6_12_TPE1
RGPS6_13_SS1
PS7_2_TPE1PS7_2_JPE1PS7_4_TPE1PS7_4_TPE2 PS7_5_TPE1 PS7_6_TPE1 PS7_7_TPE1 PS7_8_TPE1 PS7_9_TPE1PS7_10_JPE1BBA
WPS7_11_JR1
PS7_11_VFD13HP300 FPM
PS7_12_TPE1
PS7_12_VFD13HP300 FPM
PS7_12_JPE1PS7_13_VFD13HP300 FPMPS7_14_TPE1
RGPS7_14_SS1PS7_14_FIO1
BBA
MCM02

View File

@ -0,0 +1,181 @@
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
UL1_14_VFD13HP420 FPMUL1_15_VFD13HP420 FPMUL1_16_VFD13HP420 FPMUL1_17_VFD13HP420 FPM
UL1_18_VFD15HP420 FPM
UL1_19_VFD13HP420 FPM
UL1_20_VFD12HP420 FPM
UL1_18_ENC1
UL1_14_TPE1
UL1_15_TPE1
UL1_16_TPE1
UL1_17_TPE1
UL1_19_TPE2UL1_19_TPE1EPCUL1_14_EPC1
EPCUL1_14_EPC2
BBGUL1_14_S1RA
UL7_14_VFD13HP420 FPMUL7_15_VFD13HP420 FPMUL7_16_VFD13HP420 FPM
UL7_17_VFD13HP420 FPM
UL7_18_VFD15HP420 FPM
UL7_19_VFD13HP420 FPM
UL7_20_VFD13HP420 FPM
UL11_15_VFD13HP420 FPMUL11_16_VFD13HP420 FPMUL11_17_VFD13HP420 FPMUL11_18_VFD13HP420 FPM
UL11_19_VFD15HP420 FPM
UL11_20_VFD13HP420 FPM
UL11_21_VFD13HP420 FPM
EPCUL7_14_EPC1
EPCUL7_14_EPC2
EPCUL11_15_EPC1
EPCXXXX_EPC1
RBBGUL7_14_S2
BBGUL11_15_S1RA
RBBGUL1_14_S2A
RBBGUL11_15_S2
RBBGUL7_14_S1
A
NCP1_1_VFD7.5HP300 FPM
NCP1_2_VFD17.5HP300 FPM
NCP1_3_VFD17.5HP300 FPM
NCP1_4_VFD15HP300 FPM
NCP1_5_VFD17.5HP350 FPM NCP1_6_VFD15HP350 FPM NCP1_7_VFD17.5HP350 FPM NCP1_8_VFD17.5HP350 FPM
NCP_005_ENW1
EPCNCP1_1_EPC1RBB
EPCNCP1_1_EPC2
RBBA
A
EPCNCP1_2_EPC1
RBB
EPCNCP1_2_EPC2
RBB
A
EPCNCP1_3_EPC1RBB
EPCNCP1_3_EPC2
RBB
WNCP1_3_JR3ABB
EPCNCP1_8_EPC1
EPCNCP1_8_EPC2
V
GNCP1_1_S2
GNCP1_1_S1
GNCP1_2_S2GNCP1_2_S1
GNCP1_3_S2GNCP1_3_S1
NCP1_1_ENC1
NCP1_2_ENC1
NCP1_3_ENC1
NCP1_1_TPE1
NCP1_5_TPE1 NCP1_5_TPE2NCP1_6_TPE1
UL13_DPM2MCM03
UL1_DPM2MCM03
UL7_DPM2MCM03
MCM03
NCP_DPM1MCM03
UL4_14_VFD12HP420 FPMUL4_15_VFD13HP420 FPMUL4_16_VFD13HP420 FPMUL4_17_VFD13HP420 FPM
UL4_18_VFD15HP420 FPM
UL4_19_VFD13HP420 FPM
UL4_20_VFD12HP420 FPM
UL4_18_ENC1
UL4_14_TPE1
UL4_15_TPE1
UL4_16_TPE1
UL4_17_TPE1
UL4_19_TPE2UL4_19_TPE1BBGUL4_14_S1RA
RBBGUL4_14_S2A
ST 1.5kW 1.4375
ST 1.5kW 1.4375
ST 1.5kW 1.4375
UL13_13_TPE1
UL13_13_ENC1
EPCUL21_18_EPC1
EPCUL21_18_EPC2
EPCUL18_17_EPC1
EPCUL18_17_EPC2
EPCUL13_13_EPC1
EPCUL13_13_EPC2
RBBGUL21_18_S2
RBBGUL18_17_S2
RBBGUL13_13_S2
A
A
RBBGUL18_17_S1
RBBGUL21_18_S1
ARBBGUL13_13_S1
EPCUL4_14_EPC2
EPCUL4_14_EPC1
UL7_18_ENC1
UL7_14_TPE1
UL7_15_TPE1
UL7_16_TPE1
UL7_17_TPE1
UL7_19_TPE2UL7_19_TPE1
UL11_15_TPE1
UL11_16_TPE1
UL11_17_TPE1
UL11_18_TPE1
UL11_19_ENC1
UL11_20_TPE2UL11_20_TPE1
UL13_13_VFD13HP420 FPM
UL1_14_ENC1UL1_15_ENC1UL1_16_ENC1UL1_17_ENC1
UL1_19_ENC1
UL4_14_ENC1UL4_15_ENC1UL4_16_ENC1UL4_17_ENC1
UL4_19_ENC1
UL7_14_ENC1UL7_15_ENC1UL7_16_ENC1UL7_17_ENC1
UL7_19_ENC1
UL11_15_ENC1UL11_16_ENC1UL11_17_ENC1UL11_18_ENC1
UL11_20_ENC1
UL13_14_VFD13HP420 FPMUL13_15_VFD13HP420 FPMUL13_16_VFD13HP420 FPMUL13_17_VFD15HP420 FPM
UL13_18_VFD13HP420 FPM
UL13_19_VFD12HP420 FPM
UL13_14_TPE1
UL13_15_TPE1
UL13_16_TPE1
UL13_18_TPE2
UL13_18_TPE1UL13_18_ENC1
UL13_14_ENC1UL13_15_ENC1UL13_16_ENC1
UL13_17_ENC1
UL18_17_TPE1
UL18_17_ENC1UL18_17_VFD13HP420 FPMUL18_18_VFD13HP420 FPMUL18_19_VFD13HP420 FPMUL18_20_VFD13HP420 FPMUL18_21_VFD15HP420 FPM
UL18_18_TPE1
UL18_19_TPE1
UL18_20_TPE1
UL18_18_ENC1UL18_19_ENC1UL18_20_ENC1
UL18_21_ENC1
UL18_22_VFD13HP420 FPM
UL18_23_VFD12HP420 FPMUL18_22_TPE2
UL18_22_TPE1UL18_22_ENC1
UL21_18_TPE1
UL21_18_ENC1
UL21_18_VFD13HP420 FPMUL21_19_VFD13HP420 FPMUL21_20_VFD13HP420 FPMUL21_21_VFD13HP420 FPMUL21_22_VFD15HP420 FPM
UL21_19_TPE1
UL21_20_TPE1
UL21_21_TPE1
UL21_19_ENC1UL21_20_ENC1UL21_21_ENC1
UL21_22_ENC1
UL21_23_VFD13HP420 FPM
UL21_24_VFD12HP420 FPMUL21_23_TPE2
UL21_23_TPE1UL21_23_ENC1
NCP1_1_TPE2
NCP1_1_TPE3
NCP1_1_TPE4
WNCP1_1_JR1BBA
NCP1_1_TPE5
NCP1_2_TPE1
NCP1_2_TPE2
NCP1_2_TPE3
NCP1_2_TPE4
WNCP1_2_JR1BBA
NCP1_2_TPE5
WNCP1_3_JR1BBA
WNCP1_3_JR2BBA
NCP1_3_TPE1
NCP1_3_TPE2
NCP1_3_TPE3
NCP1_3_TPE4
NCP1_3_TPE5
NCP1_3_TPE6
NCP1_3_TPE7
NCP1_7_TPE1 RBB
RBB
GNCP1_8_S1
GNCP1_8_S2
NCP1_8_TPE1NCP1_8_FIO1
MCM03

View File

@ -0,0 +1,587 @@
2'-1.5" EL
2'-3" EL
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
ERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSC MDR POWER SUPPLY80AMDR POWER SUPPLY80A
V
11'-2" EL
1'-4" EL
FL_DPM1MCM04
FL_DPM2MCM04
ULGLB_DPM1MCM04
ULGLC_DPM1MCM04
MCM04
PRS1_DPM1MCM04
CH_DPM2MCM04
FL1038_2_PE1ABBB
FL1038_1CH_PE1
FL1038_2_PE2
FL1038_2_VFD15HP350FPM
FL1038_3CH_PE1BBBWFL1038_2_JR2WFL1038_2_JR1
WFL1038_1_JR1
ULC8_3_VFD12HP80FPMULC7_3_VFD12HP80FPMULC6_3_VFD12HP80FPMULC5_3_VFD12HP80FPM
PS10_1_VFD15HP150FPMPS10_2_VFD115HP200FPM
PS10_5_VFD115HP240FPM
PS11_1_VFD15HP150FPMPS11_2_VFD15HP200FPMPS11_3_VFD110HP240FPM
PS11_4_VFD110HP240FPM
PS11_6_VFD110HP240FPM PS11_7_VFD120HP240FPM PS11_8_VFD115HP240FPM PS11_9_VFD115HP240FPM
PS11_11_VFD115HP240FPMULC8_3_JPE2ULC7_3_JPE2ULC6_3_JPE2ULC5_3_JPE2
PS10_1_JPE2
PS10_1_JPE1PS10_1_JPE3PS11_1_JPE1
PS11_1_JPE2
PS11_1_JPE3PS10_3_JPE1 PS11_2_JPE1
PS10_5CH2_FPE1PS10_5CH2_JPE1
PS10_5CH5_FPE2
PS10_6CH_FPE1
PS10_5_JPE4
PS10_5_JPE5
PS10_5_JPE3
PS10_5_JPE2
PS10_5_JPE1
PS11_3_JPE2
PS11_4_JPE1
PS11_11_JPE1
PS11_11_JPE2
PS11_11_JPE3
PS11_11_JPE4
PS11_11_JPE5
PS11_11CH1_JPE1
EPCULC5_3_EPC1
EPCULC5_3_EPC2
EPCULC6_3_EPC1
EPCULC7_3_EPC1
EPCULC7_3_EPC2
EPCULC8_3_EPC1
EPCPS10_1_EPC1EPCPS11_1_EPC1
EPCPS10_5_EPC1
EPCPS11_11_EPC1
WPS10_1_JR1
WULC8_3_JR1
WULC7_3_JR1
WULC6_3_JR1
WULC5_3_JR1W
PS11_1_JR2
PS11_9_JPE1
WPS11_11_JR1
WPS11_11_JR2
WPS10_5_JR1
WPS10_5_JR2ABB
ABB
ABBR
GPS10_3_JR1
GPS11_11_S1
RABB RABB RABB RABB
ABBABBR ABBR
ABBR
ABB
ABB
PS11_1_FIO1PS10_1_FIO2
PS10_5_FIO1
PS10_5_FIO3
PS11_11_FIO1
PS10_5DIV1_LS1LSPS10_5DIV1_LS2LS
PS10_5DIV2_LS1LSPS10_5DIV2_LS2LS
PS10_5DIV3_LS1LSPS10_5DIV3_LS2LS
PS10_5DIV5_LS1LSPS10_5DIV5_LS2LS
PS11_11DIV1_LS1LSPS11_11DIV1_LS2LS
PS11_11DIV2_LS1LSPS11_11DIV2_LS2LS
PS11_11DIV3_LS1LSPS11_11DIV3_LS2LS
PS11_11DIV4_LS1LSPS11_11DIV4_LS2LS
PS11_11DIV5_LS1LSPS11_11DIV5_LS2LS
PS11_11DIV6_LS1LSPS11_11DIV6_LS2LS
SOLPS10_5DIV5_SOL1SOLPS10_5DIV5_SOL2
SOLPS10_5DIV3_SOL1SOLPS10_5DIV3_SOL2
SOLPS10_5DIV2_SOL1SOLPS10_5DIV2_SOL2SOLPS10_5DIV1_SOL1SOLPS10_5DIV1_SOL2
SOLPS11_11DIV1_SOL1SOLPS11_11DIV1_SOL2
SOLPS11_11DIV2_SOL1SOLPS11_11DIV2_SOL2
SOLPS11_11DIV3_SOL1SOLPS11_11DIV3_SOL2
SOLPS11_11DIV4_SOL1SOLPS11_11DIV4_SOL2
SOLPS11_11DIV5_SOL1SOLPS11_11DIV5_SOL2
SOLPS11_11DIV6_SOL1SOLPS11_11DIV6_SOL2
PS10_5CH2_FPE2PS10_5CH1_JPE1PS10_5CH1_FPE1PS10_5CH1_FPE2
PS10_5CH5_FPE1PS10_5CH5_JPE1
PS10_1_JPE4
PS11_11CH1_FPE1PS11_11CH1_FPE2PS11_11CH2_JPE1
PS11_12CH_FPE1ULC8_3_JPE1ULC7_3_JPE1ULC6_3_JPE1
ULC5_3_JPE1
PS11_1_JPE4
PRS3_2B_VFD12HP120 FPMPRS3_3B_VFD13HP120 FPM
PRS3_2A_VFD12HP120 FPMPRS3_3A_VFD12HP120 FPM
PRS3_5_VFD12HP120 FPM
ERSCPRS3_6_ERSC1ERSCPRS3_6_ERSC2ERSCPRS3_6_ERSC3ERSCPRS3_6_ERSC4ERSCPRS3_6_ERSC5ERSCPRS3_6_ERSC6ERSCPRS3_6_ERSC7ERSCPRS3_6_ERSC8ERSCPRS3_6_ERSC9ERSCPRS3_6_ERSC10ERSCPRS3_6_ERSC11ERSCPRS3_6_ERSC12ERSCPRS3_6_ERSC13ERSCPRS3_6_ERSC14ERSCPRS3_6_ERSC15 MDR POWER SUPPLYPRS3_6_PSU140AMDR POWER SUPPLYPRS3_6_PSU240A
PRS3_6_PE1PRS3_6_PE2PRS3_6_PE3PRS3_6_PE4PRS3_6_PE5PRS3_6_PE6PRS3_6_PE7PRS3_6_PE8PRS3_6_PE9PRS3_6_PE10PRS3_6_PE11PRS3_6_PE12PRS3_6_PE13PRS3_6_PE14PRS3_6_PE15PRS3_6_PE16PRS3_6_PE17PRS3_6_PE18PRS3_6_PE19PRS3_6_PE20PRS3_6_PE21PRS3_6_PE22PRS3_6_PE23PRS3_6_PE24PRS3_6_PE25PRS3_6_PE26PRS3_6_PE27PRS3_6_PE28PRS3_6_PE29PRS3_6_PE30
PRS3_2B_JPE1 PRS3_2A_JPE1
PRS3_1BCH_JPE1 PRS3_1ACH_JPE1
PRS3_5_JPE1
PRS3_3B_JPE1PRS3_3B_JPE2PRS3_3A_JPE2 PRS3_3A_JPE1
PRS3_5_JPE2
PRS4_1_JPE1PRS4_2_JPE1 EPCPRS3_5_EPC1
EPCPRS3_5_EPC2
EPCPRS4_1_EPC2
EPCPRS4_1_EPC1
EPCPRS4_2_EPC1
EPCPRS4_2_EPC2
GPRS4_2_S2ABBRGPRS4_1_S2GPRS4_1_S1
GPRS4_2_S1
GPRS3_5_S1GPRS3_5_S2
BBR
BBR
ABBR BBR
ABB ABB
WPRS3_3B_JR1
WPRS3_3A_JR1WPRS3_2B_JR1
WPRS3_1BCH_JR1 WPRS3_1ACH_JR1
WPRS3_2A_JR1
ABB
ABB
ABB
ABB
BABB
PRS3_4CH_FPE1
PRS3_4CH_FPE2B B
BB
GBBAB
GBBABGBBAB
GBBABGBBAB
GBBAB
GPS11_11CH1_S1
GPS11_11CH2_S1
GPS11_11CH3_S1
GPS11_11CH4_S1
GPS11_11CH5_S1
GPS11_11CH6_S1
GPS10_5CH2_S1
GPS10_5CH1_S1
GPS10_5CH5_S1
GPS10_5CH3_S1
GBBAB
GBBAB
GBBAB
GBBAB
PS10_1_ENC1PS10_2_ENC1
PS10_5_ENC1
PS11_3_ENC1
PS11_6_ENC1 PS11_7_ENC1 PS11_8_ENC1 PS11_9_ENC1
PS11_11_ENC1
EPCPS11_7_EPC2
EPCPS11_7_EPC1
BBR
BBR
G
PS11_7_S2
GPS11_7_S1
ULC8_3_ENC1ULC7_3_ENC1ULC6_3_ENC1ULC5_3_ENC1
PRS4_2_ENC1
PRS4_1_ENC1
PS10_5_PS1PS
PS11_11_PS1PS
PS10_5CH3_JPE1PS10_5CH3_FPE1PS10_5CH3_FPE2
PS11_11CH2_FPE1PS11_11CH2_FPE2
EPCPS11_8_EPC1BBR
G
PRS4_2_JPE2PS11_3_JPE1
EPCPS11_4_EPC2
EPCPS11_4_EPC1EPCPS11_3_EPC1
EPCPS11_3_EPC2
ABBR
BBR
BBR
GPS11_3_S1ABBR
GPS11_3_S2
PS11_2_ENC1PS10_3_VFD115HP240FPMPS10_3_ENC1 PS11_1_ENC1
PS11_4_ENC1
PS11_11_JPE6WPS11_11_JR3ABB
PRS4_1_VFD17.5HP120FPM
PRS4_2_VFD15HP120FPM
RGULC5_3_SS1RGULC5_3_SS2
PS11_1_FIO2
RGULC6_3_SS2
PS10_1_FIO1
RGPS10_1_SS1
PS10_2_TPE
PS10_5_FIO2
PS10_5_FIO4PS10_5_FIO5
WPS11_1_JR1
PS11_3_FIO1
GPS11_4_S2
GPS11_4_S1
PS11_4_FIO1
PS11_7_FIO1
PS11_11_FIO2
PS11_11_FIO3
PS11_11_FIO4
PS11_11_FIO5
PS11_11_FIO6
PRS3_2B_FIO1 PRS3_2A_FIOM1
PRS3_4CH_FIO1
PRS4_2_FIO1
WPRS4_2_JR1
RGULC7_3_SS1
RGULC7_3_SS2RGULC8_3_SS1
PS10_5_TPE1
PS11_6_TPE1
PS11_6_TPE2
PS11_7_TPE1
PS11_8_TPE1 PS11_11_TPE1
PS11_11CH3_JPE1PS11_11CH3_FPE1PS11_11CH3_FPE2PS11_11CH4_JPE1PS11_11CH4_FPE1PS11_11CH4_FPE2
PS11_11CH5_JPE1PS11_11CH5_FPE1PS11_11CH5_FPE2PS11_11CH6_JPE1PS11_11CH6_FPE1PS11_11CH6_FPE2
GDTC_NCH1_EN1
CH_DPM1_FIOM1
GDTC_NCH2_EN1
GDTC_NCH3_EN1
GDTC_NCH4_EN1CH_DPM1MCM04
GDTC_NCH5_EN1
CH_DPM1_FIOM2
GDTC_NCH6_EN1
GDTC_NCH7_EN1
GDTC_NCH8_EN1
GDTC_NCH9_EN1
CH_DPM1_FIOM3
GDTC_NCH10_EN1
GDTC_NCH11_EN1
GDTC_NCH12_EN1
GDTC_NCH13_EN1
CH_DPM1_FIOM4
GDTC_NCH14_EN1
GDTC_NCH15_EN1
GDTC_NCH16_EN1
CH_DPM1_FIOM5
GDTC_NCH17_EN1
FL1034_2_PE1ABBBFL1034_1CH_PE1
FL1034_2_PE2
FL1034_2_VFD15HP350FPM
FL1034_3CH_PE1BBBWFL1034_2_JR2WFL1034_2_JR1
WFL1034_1_JR1FL1026_2_PE1ABBB
FL1026_1CH_PE1
FL1026_2_PE2FL1026_3CH_PE1BBBWFL1026_2_JR2WFL1026_2_JR1
WFL1026_1_JR1FL1022_2_PE1
ABBB
FL1022_1CH_PE1
FL1022_2_PE2FL1022_3CH_PE1BBBWFL1022_2_JR2WFL1022_2_JR1
WFL1022_1_JR1FL1018_2_PE1ABBB
FL1018_1CH_PE1
FL1018_2_PE2FL1018_3CH_PE1BBBWFL1018_2_JR2WFL1018_2_JR1
WFL1018_1_JR1
FL1014_2_PE1ABBB
FL1014_1CH_PE1
FL1014_2_PE2FL1014_3CH_PE1BBBWFL1014_2_JR2WFL1014_2_JR1
WFL1014_1_JR1 FL3012_2_PE1
ABBB
FL3012_1CH_PE1
FL3012_2_PE2FL3012_3CH_PE1BBBWFL3012_2_JR2WFL3012_2_JR1
WFL3012_1_JR1FL3016_2_PE1
ABBB
FL3016_1CH_PE1
FL3016_2_PE2FL3016_3CH_PE1BBBWFL3016_2_JR2WFL3016_2_JR1
WFL3016_1_JR1FL3020_2_PE1
ABBB
FL3020_1CH_PE1
FL3020_2_PE2FL3020_3CH_PE1BBBWFL3020_2_JR2WFL3020_2_JR1
WFL3020_1_JR1FL3024_2_PE1
ABBB
FL3024_1CH_PE1
FL3024_2_PE2FL3024_3CH_PE1BBBWFL3024_2_JR2WFL3024_2_JR1
WFL3024_1_JR1
FL1026_2_VFD15HP350FPMFL1022_2_VFD15HP350FPMFL1018_2_VFD15HP350FPMFL1014_2_VFD15HP350FPM FL3012_2_VFD15HP350FPMFL3016_2_VFD15HP350FPMFL3020_2_VFD15HP350FPMFL3024_2_VFD15HP350FPM
CH_DPM2_FIOM2CH_DPM2_FIOM1
CH_DPM2_FIOM4CH_DPM2_FIOM3CH_DPM2_FIOM6CH_DPM2_FIOM5
BBGABBBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGBBBGAB BBGAB BBGAB BBGAB
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB
CH_DPM3MCM04 CH_DPM4MCM04 CH_DPM5MCM04 CH_DPM6MCM04
PB_NCH1_FIOH1PB_NCH3_FIOH1PB_NCH5_FIOH1PB_NCH7_FIOH1PB_NCH9_FIOH1PB_NCH10_FIOH1PB_NCH11_FIOH1PB_NCH12_FIOH1PB_NCH13_FIOH1PB_NCH14_FIOH1PB_NCH15_FIOH1PB_NCH16_FIOH1PB_NCH18_FIOH1PB_NCH20_FIOH1
CH_DPM3_FIOM2CH_DPM3_FIOM1CH_DPM3_FIOM4CH_DPM3_FIOM3CH_DPM3_FIOM6CH_DPM3_FIOM5CH_DPM3_FIOM8CH_DPM3_FIOM7
PB_NCH21_FIOH1PB_NCH24_FIOH1PB_NCH25_FIOH1PB_NCH26_FIOH1PB_NCH27_FIOH1PB_NCH28_FIOH1PB_NCH29_FIOH1PB_NCH31_FIOH1PB_NCH33_FIOH1PB_NCH35_FIOH1PB_NCH37_FIOH1PB_NCH39_FIOH1PB_NCH41_FIOH1PB_NCH43_FIOH1PB_NCH45_FIOH1PB_NCH46_FIOH1PB_NCH48_FIOH1PB_NCH50_FIOH1PB_NCH52_FIOH1PB_NCH54_FIOH1PB_NCH56_FIOH1PB_NCH58_FIOH1PB_NCH60_FIOH1
CH_DPM4_FIOM1CH_DPM4_FIOM3CH_DPM4_FIOM2CH_DPM4_FIOM5CH_DPM4_FIOM4CH_DPM4_FIOM7CH_DPM4_FIOM6CH_DPM5_FIOM2CH_DPM5_FIOM1CH_DPM5_FIOM4CH_DPM5_FIOM3CH_DPM5_FIOM6CH_DPM5_FIOM5CH_DPM5_FIOM8CH_DPM5_FIOM7CH_DPM6_FIOM2CH_DPM6_FIOM1CH_DPM6_FIOM4CH_DPM6_FIOM3CH_DPM6_FIOM6CH_DPM6_FIOM5CH_DPM6_FIOM8CH_DPM6_FIOM7
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGABBBGBBBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGB BBGB BBGBBBGBBBGB BBGB BBGBBBGB BBGB BBGB BBGB BBGB BBGB BBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGB BBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGB
GDTC_NCH18_EN1
GDTC_NCH19_EN1
GDTC_NCH20_EN1
GDTC_NCH21_EN1
GDTC_NCH22_EN1
GDTC_NCH23_EN1
GDTC_NCH24_EN1
GDTC_NCH25_EN1
GDTC_NCH26_EN1
GDTC_NCH27_EN1
GDTC_NCH28_EN1
GDTC_NCH29_EN1
GDTC_NCH30_EN1
GDTC_NCH31_EN1
GDTC_NCH32_EN1
GDTC_NCH33_EN1
GDTC_NCH34_EN1
GDTC_NCH35_EN1
GDTC_NCH36_EN1
GDTC_NCH37_EN1
GDTC_NCH38_EN1
GDTC_NCH39_EN1
GDTC_CH40_EN1
GDTC_CH41_EN1
GDTC_CH42_EN1
GDTC_CH43_EN1
GDTC_CH44_EN1
GDTC_CH45_EN1
GDTC_CH46_EN1
GDTC_CH47_EN1
GDTC_CH48_EN1
GDTC_CH49_EN1
GDTC_CH50_EN1
GDTC_NCH51_EN1
GDTC_NCH52_EN1
GDTC_NCH53_EN1
GDTC_NCH54_EN1
GDTC_NCH55_EN1
GDTC_NCH56_EN1
GDTC_NCH57_EN1
GDTC_NCH58_EN1
GDTC_NCH59_EN1
GDTC_NCH60_EN1
GDTC_NCH61_EN1
GDTC_NCH62_EN1
GDTC_NCH63_EN1
GDTC_NCH64_EN1
GDTC_NCH65_EN1
GDTC_NCH66_EN1
GDTC_NCH67_EN1
GDTC_NCH68_EN1
GDTC_NCH69_EN1
GDTC_NCH70_EN1
GDTC_NCH71_EN1
GDTC_NCH72_EN1
GDTC_NCH73_EN1
GDTC_NCH74_EN1
GDTC_NCH75_EN1
GDTC_NCH76_EN1
PB_NCH1_PE1
PB_NCH1_PE2
BBGABBBGB
WPB_NCH1_PKGREL_PB1SOLPB_NCH1_PKGREL_SOL1
PB_NCH2_PE1
PB_NCH2_PE2
WPB_NCH2_PKGREL_PB1SOLPB_NCH2_PKGREL_SOL1
PB_NCH3_PE1
PB_NCH3_PE2
WPB_NCH3_PKGREL_PB1SOLPB_NCH3_PKGREL_SOL1
PB_NCH4_PE1
PB_NCH4_PE2
WPB_NCH4_PKGREL_PB1SOLPB_NCH4_PKGREL_SOL1
PB_NCH5_PE1
PB_NCH5_PE2
WPB_NCH5_PKGREL_PB1SOLPB_NCH5_PKGREL_SOL1
PB_NCH6_PE1
PB_NCH6_PE2
WPB_NCH6_PKGREL_PB1SOLPB_NCH6_PKGREL_SOL1
PB_NCH7_PE1
PB_NCH7_PE2
WPB_NCH7_PKGREL_PB1SOLPB_NCH7_PKGREL_SOL1
PB_NCH8_PE1
PB_NCH8_PE2
WPB_NCH8_PKGREL_PB1SOLPB_NCH8_PKGREL_SOL1
PB_NCH9_PE1
PB_NCH9_PE2
WPB_NCH9_PKGREL_PB1SOLPB_NCH9_PKGREL_SOL1
PB_NCH10_PE1
PB_NCH10_PE2
WPB_NCH10_PKGREL_PB1SOLPB_NCH10_PKGREL_SOL1
PB_NCH11_PE1
PB_NCH11_PE2
WPB_NCH11_PKGREL_PB1SOLPB_NCH11_PKGREL_SOL1
PB_NCH12_PE1
PB_NCH12_PE2
WPB_NCH12_PKGREL_PB1SOLPB_NCH12_PKGREL_SOL1
PB_NCH13_PE1
PB_NCH13_PE2
WPB_NCH13_PKGREL_PB1SOLPB_NCH13_PKGREL_SOL1
PB_NCH14_PE1
PB_NCH14_PE2
WPB_NCH14_PKGREL_PB1SOLPB_NCH14_PKGREL_SOL1
PB_NCH15_PE1
PB_NCH15_PE2
WPB_NCH15_PKGREL_PB1SOLPB_NCH15_PKGREL_SOL1
PB_NCH16_PE1
PB_NCH16_PE2
WPB_NCH16_PKGREL_PB1SOLPB_NCH16_PKGREL_SOL1
PB_NCH17_PE1
PB_NCH17_PE2
WPB_NCH17_PKGREL_PB1SOLPB_NCH17_PKGREL_SOL1
PB_NCH18_PE1
PB_NCH18_PE2
WPB_NCH18_PKGREL_PB1SOLPB_NCH18_PKGREL_SOL1
PB_NCH19_PE1
PB_NCH19_PE2
WPB_NCH19_PKGREL_PB1SOLPB_NCH19_PKGREL_SOL1
PB_NCH20_PE1
PB_NCH20_PE2
WPB_NCH20_PKGREL_PB1SOLPB_NCH20_PKGREL_SOL1
PB_NCH21_PE1
PB_NCH21_PE2
WPB_NCH21_PKGREL_PB1SOLPB_NCH21_PKGREL_SOL1
PB_NCH22_PE1
PB_NCH22_PE2
WPB_NCH22_PKGREL_PB1SOLPB_NCH22_PKGREL_SOL1
PB_NCH23_PE1
PB_NCH23_PE2
WPB_NCH23_PKGREL_PB1SOLPB_NCH23_PKGREL_SOL1
PB_NCH24_PE1
PB_NCH24_PE2
WPB_NCH24_PKGREL_PB1SOLPB_NCH24_PKGREL_SOL1
PB_NCH25_PE1
PB_NCH25_PE2
WPB_NCH25_PKGREL_PB1SOLPB_NCH25_PKGREL_SOL1
PB_NCH26_PE1
PB_NCH26_PE2
WPB_NCH26_PKGREL_PB1SOLPB_NCH26_PKGREL_SOL1
PB_NCH27_PE1
PB_NCH27_PE2
WPB_NCH27_PKGREL_PB1SOLPB_NCH27_PKGREL_SOL1
PB_NCH28_PE1
PB_NCH28_PE2
WPB_NCH28_PKGREL_PB1SOLPB_NCH28_PKGREL_SOL1
PB_NCH29_PE1
PB_NCH29_PE2
WPB_NCH29_PKGREL_PB1SOLPB_NCH29_PKGREL_SOL1
PB_NCH30_PE1
PB_NCH30_PE2
WPB_NCH30_PKGREL_PB1SOLPB_NCH30_PKGREL_SOL1
PB_NCH31_PE1
PB_NCH31_PE2
WPB_NCH31_PKGREL_PB1SOLPB_NCH31_PKGREL_SOL1
PB_NCH32_PE1
PB_NCH32_PE2
WPB_NCH32_PKGREL_PB1SOLPB_NCH32_PKGREL_SOL1
PB_NCH33_PE1
PB_NCH33_PE2
WPB_NCH33_PKGREL_PB1SOLPB_NCH33_PKGREL_SOL1
PB_NCH34_PE1
PB_NCH34_PE2
WPB_NCH34_PKGREL_PB1SOLPB_NCH34_PKGREL_SOL1
PB_NCH35_PE1
PB_NCH35_PE2
WPB_NCH35_PKGREL_PB1SOLPB_NCH35_PKGREL_SOL1
PB_NCH36_PE1
PB_NCH36_PE2
WPB_NCH36_PKGREL_PB1SOLPB_NCH36_PKGREL_SOL1
PB_NCH37_PE1
PB_NCH37_PE2
WPB_NCH37_PKGREL_PB1SOLPB_NCH37_PKGREL_SOL1
PB_NCH38_PE1
PB_NCH38_PE2
WPB_NCH38_PKGREL_PB1SOLPB_NCH38_PKGREL_SOL1
PB_NCH39_PE1
PB_NCH39_PE2
WPB_NCH39_PKGREL_PB1SOLPB_NCH39_PKGREL_SOL1
PB_NCH40_PE1
PB_NCH40_PE2
WPB_NCH40_PKGREL_PB1SOLPB_NCH40_PKGREL_SOL1
PB_NCH41_PE1
PB_NCH41_PE2
WPB_NCH41_PKGREL_PB1SOLPB_NCH41_PKGREL_SOL1
PB_NCH42_PE1
PB_NCH42_PE2
WPB_NCH42_PKGREL_PB1SOLPB_NCH42_PKGREL_SOL1
PB_NCH43_PE1
PB_NCH43_PE2
WPB_NCH43_PKGREL_PB1SOLPB_NCH43_PKGREL_SOL1
PB_NCH44_PE1
PB_NCH44_PE2
WPB_NCH44_PKGREL_PB1SOLPB_NCH44_PKGREL_SOL1
PB_NCH45_PE1
PB_NCH45_PE2
WPB_NCH45_PKGREL_PB1SOLPB_NCH45_PKGREL_SOL1
PB_NCH46_PE1
PB_NCH46_PE2
WPB_NCH46_PKGREL_PB1SOLPB_NCH46_PKGREL_SOL1
PB_NCH47_PE1
PB_NCH47_PE2
WPB_NCH47_PKGREL_PB1SOLPB_NCH47_PKGREL_SOL1
PB_NCH48_PE1
PB_NCH48_PE2
WPB_NCH48_PKGREL_PB1SOLPB_NCH48_PKGREL_SOL1
PB_NCH49_PE1
PB_NCH49_PE2
WPB_NCH49_PKGREL_PB1SOLPB_NCH49_PKGREL_SOL1
PB_NCH50_PE1
PB_NCH50_PE2
WPB_NCH50_PKGREL_PB1SOLPB_NCH50_PKGREL_SOL1
PB_NCH51_PE1
PB_NCH51_PE2
WPB_NCH51_PKGREL_PB1SOLPB_NCH51_PKGREL_SOL1
PB_NCH52_PE1
PB_NCH52_PE2
WPB_NCH52_PKGREL_PB1SOLPB_NCH52_PKGREL_SOL1
PB_NCH53_PE1
PB_NCH53_PE2
WPB_NCH53_PKGREL_PB1SOLPB_NCH53_PKGREL_SOL1
PB_NCH54_PE1
PB_NCH54_PE2
WPB_NCH54_PKGREL_PB1SOLPB_NCH54_PKGREL_SOL1
PB_NCH55_PE1
PB_NCH55_PE2
WPB_NCH55_PKGREL_PB1SOLPB_NCH55_PKGREL_SOL1
PB_NCH56_PE1
PB_NCH56_PE2
WPB_NCH56_PKGREL_PB1SOLPB_NCH56_PKGREL_SOL1
PB_NCH57_PE1
PB_NCH57_PE2
WPB_NCH57_PKGREL_PB1SOLPB_NCH57_PKGREL_SOL1
PB_NCH58_PE1
PB_NCH58_PE2
WPB_NCH58_PKGREL_PB1SOLPB_NCH58_PKGREL_SOL1
PB_NCH59_PE1
PB_NCH59_PE2
WPB_NCH59_PKGREL_PB1SOLPB_NCH59_PKGREL_SOL1
PB_NCH60_PE1
PB_NCH60_PE2
WPB_NCH60_PKGREL_PB1SOLPB_NCH60_PKGREL_SOL1
PB_NCH61_PE1
PB_NCH61_PE2
WPB_NCH61_PKGREL_PB1SOLPB_NCH61_PKGREL_SOL1
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGABBBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
projects/MTN6/repo Submodule

@ -0,0 +1 @@
Subproject commit 456de12cca56c09bc1881660b163ac3b5dff593a

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,587 @@
2'-1.5" EL
2'-3" EL
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
TRASH
PALLETSTAGINGAREA
PROBLEM SOLV.CART 80/20
PROB. SOLV. CART
TRASH
ERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSCERSC MDR POWER SUPPLY80AMDR POWER SUPPLY80A
V
11'-2" EL
1'-4" EL
FL_DPM1MCM04
FL_DPM2MCM04
ULGLB_DPM1MCM04
ULGLC_DPM1MCM04
MCM04
PRS1_DPM1MCM04
CH_DPM2MCM04
FL1038_2_PE1ABBB
FL1038_1CH_PE1
FL1038_2_PE2
FL1038_2_VFD15HP350FPM
FL1038_3CH_PE1BBBWFL1038_2_JR2WFL1038_2_JR1
WFL1038_1_JR1
ULC8_3_VFD12HP80FPMULC7_3_VFD12HP80FPMULC6_3_VFD12HP80FPMULC5_3_VFD12HP80FPM
PS10_1_VFD15HP150FPMPS10_2_VFD115HP200FPM
PS10_5_VFD115HP240FPM
PS11_1_VFD15HP150FPMPS11_2_VFD15HP200FPMPS11_3_VFD110HP240FPM
PS11_4_VFD110HP240FPM
PS11_6_VFD110HP240FPM PS11_7_VFD120HP240FPM PS11_8_VFD115HP240FPM PS11_9_VFD115HP240FPM
PS11_11_VFD115HP240FPMULC8_3_JPE2ULC7_3_JPE2ULC6_3_JPE2ULC5_3_JPE2
PS10_1_JPE2
PS10_1_JPE1PS10_1_JPE3PS11_1_JPE1
PS11_1_JPE2
PS11_1_JPE3PS10_3_JPE1 PS11_2_JPE1
PS10_5CH2_FPE1PS10_5CH2_JPE1
PS10_5CH5_FPE2
PS10_6CH_FPE1
PS10_5_JPE4
PS10_5_JPE5
PS10_5_JPE3
PS10_5_JPE2
PS10_5_JPE1
PS11_3_JPE2
PS11_4_JPE1
PS11_11_JPE1
PS11_11_JPE2
PS11_11_JPE3
PS11_11_JPE4
PS11_11_JPE5
PS11_11CH1_JPE1
EPCULC5_3_EPC1
EPCULC5_3_EPC2
EPCULC6_3_EPC1
EPCULC7_3_EPC1
EPCULC7_3_EPC2
EPCULC8_3_EPC1
EPCPS10_1_EPC1EPCPS11_1_EPC1
EPCPS10_5_EPC1
EPCPS11_11_EPC1
WPS10_1_JR1
WULC8_3_JR1
WULC7_3_JR1
WULC6_3_JR1
WULC5_3_JR1W
PS11_1_JR2
PS11_9_JPE1
WPS11_11_JR1
WPS11_11_JR2
WPS10_5_JR1
WPS10_5_JR2ABB
ABB
ABBR
GPS10_3_JR1
GPS11_11_S1
RABB RABB RABB RABB
ABBABBR ABBR
ABBR
ABB
ABB
PS11_1_FIO1PS10_1_FIO2
PS10_5_FIO1
PS10_5_FIO3
PS11_11_FIO1
PS10_5DIV1_LS1LSPS10_5DIV1_LS2LS
PS10_5DIV2_LS1LSPS10_5DIV2_LS2LS
PS10_5DIV3_LS1LSPS10_5DIV3_LS2LS
PS10_5DIV5_LS1LSPS10_5DIV5_LS2LS
PS11_11DIV1_LS1LSPS11_11DIV1_LS2LS
PS11_11DIV2_LS1LSPS11_11DIV2_LS2LS
PS11_11DIV3_LS1LSPS11_11DIV3_LS2LS
PS11_11DIV4_LS1LSPS11_11DIV4_LS2LS
PS11_11DIV5_LS1LSPS11_11DIV5_LS2LS
PS11_11DIV6_LS1LSPS11_11DIV6_LS2LS
SOLPS10_5DIV5_SOL1SOLPS10_5DIV5_SOL2
SOLPS10_5DIV3_SOL1SOLPS10_5DIV3_SOL2
SOLPS10_5DIV2_SOL1SOLPS10_5DIV2_SOL2SOLPS10_5DIV1_SOL1SOLPS10_5DIV1_SOL2
SOLPS11_11DIV1_SOL1SOLPS11_11DIV1_SOL2
SOLPS11_11DIV2_SOL1SOLPS11_11DIV2_SOL2
SOLPS11_11DIV3_SOL1SOLPS11_11DIV3_SOL2
SOLPS11_11DIV4_SOL1SOLPS11_11DIV4_SOL2
SOLPS11_11DIV5_SOL1SOLPS11_11DIV5_SOL2
SOLPS11_11DIV6_SOL1SOLPS11_11DIV6_SOL2
PS10_5CH2_FPE2PS10_5CH1_JPE1PS10_5CH1_FPE1PS10_5CH1_FPE2
PS10_5CH5_FPE1PS10_5CH5_JPE1
PS10_1_JPE4
PS11_11CH1_FPE1PS11_11CH1_FPE2PS11_11CH2_JPE1
PS11_12CH_FPE1ULC8_3_JPE1ULC7_3_JPE1ULC6_3_JPE1
ULC5_3_JPE1
PS11_1_JPE4
PRS3_2B_VFD12HP120 FPMPRS3_3B_VFD13HP120 FPM
PRS3_2A_VFD12HP120 FPMPRS3_3A_VFD12HP120 FPM
PRS3_5_VFD12HP120 FPM
ERSCPRS3_6_ERSC1ERSCPRS3_6_ERSC2ERSCPRS3_6_ERSC3ERSCPRS3_6_ERSC4ERSCPRS3_6_ERSC5ERSCPRS3_6_ERSC6ERSCPRS3_6_ERSC7ERSCPRS3_6_ERSC8ERSCPRS3_6_ERSC9ERSCPRS3_6_ERSC10ERSCPRS3_6_ERSC11ERSCPRS3_6_ERSC12ERSCPRS3_6_ERSC13ERSCPRS3_6_ERSC14ERSCPRS3_6_ERSC15 MDR POWER SUPPLYPRS3_6_PSU140AMDR POWER SUPPLYPRS3_6_PSU240A
PRS3_6_PE1PRS3_6_PE2PRS3_6_PE3PRS3_6_PE4PRS3_6_PE5PRS3_6_PE6PRS3_6_PE7PRS3_6_PE8PRS3_6_PE9PRS3_6_PE10PRS3_6_PE11PRS3_6_PE12PRS3_6_PE13PRS3_6_PE14PRS3_6_PE15PRS3_6_PE16PRS3_6_PE17PRS3_6_PE18PRS3_6_PE19PRS3_6_PE20PRS3_6_PE21PRS3_6_PE22PRS3_6_PE23PRS3_6_PE24PRS3_6_PE25PRS3_6_PE26PRS3_6_PE27PRS3_6_PE28PRS3_6_PE29PRS3_6_PE30
PRS3_2B_JPE1 PRS3_2A_JPE1
PRS3_1BCH_JPE1 PRS3_1ACH_JPE1
PRS3_5_JPE1
PRS3_3B_JPE1PRS3_3B_JPE2PRS3_3A_JPE2 PRS3_3A_JPE1
PRS3_5_JPE2
PRS4_1_JPE1PRS4_2_JPE1 EPCPRS3_5_EPC1
EPCPRS3_5_EPC2
EPCPRS4_1_EPC2
EPCPRS4_1_EPC1
EPCPRS4_2_EPC1
EPCPRS4_2_EPC2
GPRS4_2_S2ABBRGPRS4_1_S2GPRS4_1_S1
GPRS4_2_S1
GPRS3_5_S1GPRS3_5_S2
BBR
BBR
ABBR BBR
ABB ABB
WPRS3_3B_JR1
WPRS3_3A_JR1WPRS3_2B_JR1
WPRS3_1BCH_JR1 WPRS3_1ACH_JR1
WPRS3_2A_JR1
ABB
ABB
ABB
ABB
BABB
PRS3_4CH_FPE1
PRS3_4CH_FPE2B B
BB
GBBAB
GBBABGBBAB
GBBABGBBAB
GBBAB
GPS11_11CH1_S1
GPS11_11CH2_S1
GPS11_11CH3_S1
GPS11_11CH4_S1
GPS11_11CH5_S1
GPS11_11CH6_S1
GPS10_5CH2_S1
GPS10_5CH1_S1
GPS10_5CH5_S1
GPS10_5CH3_S1
GBBAB
GBBAB
GBBAB
GBBAB
PS10_1_ENC1PS10_2_ENC1
PS10_5_ENC1
PS11_3_ENC1
PS11_6_ENC1 PS11_7_ENC1 PS11_8_ENC1 PS11_9_ENC1
PS11_11_ENC1
EPCPS11_7_EPC2
EPCPS11_7_EPC1
BBR
BBR
G
PS11_7_S2
GPS11_7_S1
ULC8_3_ENC1ULC7_3_ENC1ULC6_3_ENC1ULC5_3_ENC1
PRS4_2_ENC1
PRS4_1_ENC1
PS10_5_PS1PS
PS11_11_PS1PS
PS10_5CH3_JPE1PS10_5CH3_FPE1PS10_5CH3_FPE2
PS11_11CH2_FPE1PS11_11CH2_FPE2
EPCPS11_8_EPC1BBR
G
PRS4_2_JPE2PS11_3_JPE1
EPCPS11_4_EPC2
EPCPS11_4_EPC1EPCPS11_3_EPC1
EPCPS11_3_EPC2
ABBR
BBR
BBR
GPS11_3_S1ABBR
GPS11_3_S2
PS11_2_ENC1PS10_3_VFD115HP240FPMPS10_3_ENC1 PS11_1_ENC1
PS11_4_ENC1
PS11_11_JPE6WPS11_11_JR3ABB
PRS4_1_VFD17.5HP120FPM
PRS4_2_VFD15HP120FPM
RGULC5_3_SS1RGULC5_3_SS2
PS11_1_FIO2
RGULC6_3_SS2
PS10_1_FIO1
RGPS10_1_SS1
PS10_2_TPE
PS10_5_FIO2
PS10_5_FIO4PS10_5_FIO5
WPS11_1_JR1
PS11_3_FIO1
GPS11_4_S2
GPS11_4_S1
PS11_4_FIO1
PS11_7_FIO1
PS11_11_FIO2
PS11_11_FIO3
PS11_11_FIO4
PS11_11_FIO5
PS11_11_FIO6
PRS3_2B_FIO1 PRS3_2A_FIOM1
PRS3_4CH_FIO1
PRS4_2_FIO1
WPRS4_2_JR1
RGULC7_3_SS1
RGULC7_3_SS2RGULC8_3_SS1
PS10_5_TPE1
PS11_6_TPE1
PS11_6_TPE2
PS11_7_TPE1
PS11_8_TPE1 PS11_11_TPE1
PS11_11CH3_JPE1PS11_11CH3_FPE1PS11_11CH3_FPE2PS11_11CH4_JPE1PS11_11CH4_FPE1PS11_11CH4_FPE2
PS11_11CH5_JPE1PS11_11CH5_FPE1PS11_11CH5_FPE2PS11_11CH6_JPE1PS11_11CH6_FPE1PS11_11CH6_FPE2
GDTC_NCH1_EN1
CH_DPM1_FIOM1
GDTC_NCH2_EN1
GDTC_NCH3_EN1
GDTC_NCH4_EN1CH_DPM1MCM04
GDTC_NCH5_EN1
CH_DPM1_FIOM2
GDTC_NCH6_EN1
GDTC_NCH7_EN1
GDTC_NCH8_EN1
GDTC_NCH9_EN1
CH_DPM1_FIOM3
GDTC_NCH10_EN1
GDTC_NCH11_EN1
GDTC_NCH12_EN1
GDTC_NCH13_EN1
CH_DPM1_FIOM4
GDTC_NCH14_EN1
GDTC_NCH15_EN1
GDTC_NCH16_EN1
CH_DPM1_FIOM5
GDTC_NCH17_EN1
FL1034_2_PE1ABBBFL1034_1CH_PE1
FL1034_2_PE2
FL1034_2_VFD15HP350FPM
FL1034_3CH_PE1BBBWFL1034_2_JR2WFL1034_2_JR1
WFL1034_1_JR1FL1026_2_PE1ABBB
FL1026_1CH_PE1
FL1026_2_PE2FL1026_3CH_PE1BBBWFL1026_2_JR2WFL1026_2_JR1
WFL1026_1_JR1FL1022_2_PE1
ABBB
FL1022_1CH_PE1
FL1022_2_PE2FL1022_3CH_PE1BBBWFL1022_2_JR2WFL1022_2_JR1
WFL1022_1_JR1FL1018_2_PE1ABBB
FL1018_1CH_PE1
FL1018_2_PE2FL1018_3CH_PE1BBBWFL1018_2_JR2WFL1018_2_JR1
WFL1018_1_JR1
FL1014_2_PE1ABBB
FL1014_1CH_PE1
FL1014_2_PE2FL1014_3CH_PE1BBBWFL1014_2_JR2WFL1014_2_JR1
WFL1014_1_JR1 FL3012_2_PE1
ABBB
FL3012_1CH_PE1
FL3012_2_PE2FL3012_3CH_PE1BBBWFL3012_2_JR2WFL3012_2_JR1
WFL3012_1_JR1FL3016_2_PE1
ABBB
FL3016_1CH_PE1
FL3016_2_PE2FL3016_3CH_PE1BBBWFL3016_2_JR2WFL3016_2_JR1
WFL3016_1_JR1FL3020_2_PE1
ABBB
FL3020_1CH_PE1
FL3020_2_PE2FL3020_3CH_PE1BBBWFL3020_2_JR2WFL3020_2_JR1
WFL3020_1_JR1FL3024_2_PE1
ABBB
FL3024_1CH_PE1
FL3024_2_PE2FL3024_3CH_PE1BBBWFL3024_2_JR2WFL3024_2_JR1
WFL3024_1_JR1
FL1026_2_VFD15HP350FPMFL1022_2_VFD15HP350FPMFL1018_2_VFD15HP350FPMFL1014_2_VFD15HP350FPM FL3012_2_VFD15HP350FPMFL3016_2_VFD15HP350FPMFL3020_2_VFD15HP350FPMFL3024_2_VFD15HP350FPM
CH_DPM2_FIOM2CH_DPM2_FIOM1
CH_DPM2_FIOM4CH_DPM2_FIOM3CH_DPM2_FIOM6CH_DPM2_FIOM5
BBGABBBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGBBBGAB BBGAB BBGAB BBGAB
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB
CH_DPM3MCM04 CH_DPM4MCM04 CH_DPM5MCM04 CH_DPM6MCM04
PB_NCH1_FIOH1PB_NCH3_FIOH1PB_NCH5_FIOH1PB_NCH7_FIOH1PB_NCH9_FIOH1PB_NCH10_FIOH1PB_NCH11_FIOH1PB_NCH12_FIOH1PB_NCH13_FIOH1PB_NCH14_FIOH1PB_NCH15_FIOH1PB_NCH16_FIOH1PB_NCH18_FIOH1PB_NCH20_FIOH1
CH_DPM3_FIOM2CH_DPM3_FIOM1CH_DPM3_FIOM4CH_DPM3_FIOM3CH_DPM3_FIOM6CH_DPM3_FIOM5CH_DPM3_FIOM8CH_DPM3_FIOM7
PB_NCH21_FIOH1PB_NCH24_FIOH1PB_NCH25_FIOH1PB_NCH26_FIOH1PB_NCH27_FIOH1PB_NCH28_FIOH1PB_NCH29_FIOH1PB_NCH31_FIOH1PB_NCH33_FIOH1PB_NCH35_FIOH1PB_NCH37_FIOH1PB_NCH39_FIOH1PB_NCH41_FIOH1PB_NCH43_FIOH1PB_NCH45_FIOH1PB_NCH46_FIOH1PB_NCH48_FIOH1PB_NCH50_FIOH1PB_NCH52_FIOH1PB_NCH54_FIOH1PB_NCH56_FIOH1PB_NCH58_FIOH1PB_NCH60_FIOH1
CH_DPM4_FIOM1CH_DPM4_FIOM3CH_DPM4_FIOM2CH_DPM4_FIOM5CH_DPM4_FIOM4CH_DPM4_FIOM7CH_DPM4_FIOM6CH_DPM5_FIOM2CH_DPM5_FIOM1CH_DPM5_FIOM4CH_DPM5_FIOM3CH_DPM5_FIOM6CH_DPM5_FIOM5CH_DPM5_FIOM8CH_DPM5_FIOM7CH_DPM6_FIOM2CH_DPM6_FIOM1CH_DPM6_FIOM4CH_DPM6_FIOM3CH_DPM6_FIOM6CH_DPM6_FIOM5CH_DPM6_FIOM8CH_DPM6_FIOM7
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGABBBGBBBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGB BBGB BBGBBBGBBBGB BBGB BBGBBBGB BBGB BBGB BBGB BBGB BBGB BBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGB BBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGB
GDTC_NCH18_EN1
GDTC_NCH19_EN1
GDTC_NCH20_EN1
GDTC_NCH21_EN1
GDTC_NCH22_EN1
GDTC_NCH23_EN1
GDTC_NCH24_EN1
GDTC_NCH25_EN1
GDTC_NCH26_EN1
GDTC_NCH27_EN1
GDTC_NCH28_EN1
GDTC_NCH29_EN1
GDTC_NCH30_EN1
GDTC_NCH31_EN1
GDTC_NCH32_EN1
GDTC_NCH33_EN1
GDTC_NCH34_EN1
GDTC_NCH35_EN1
GDTC_NCH36_EN1
GDTC_NCH37_EN1
GDTC_NCH38_EN1
GDTC_NCH39_EN1
GDTC_CH40_EN1
GDTC_CH41_EN1
GDTC_CH42_EN1
GDTC_CH43_EN1
GDTC_CH44_EN1
GDTC_CH45_EN1
GDTC_CH46_EN1
GDTC_CH47_EN1
GDTC_CH48_EN1
GDTC_CH49_EN1
GDTC_CH50_EN1
GDTC_NCH51_EN1
GDTC_NCH52_EN1
GDTC_NCH53_EN1
GDTC_NCH54_EN1
GDTC_NCH55_EN1
GDTC_NCH56_EN1
GDTC_NCH57_EN1
GDTC_NCH58_EN1
GDTC_NCH59_EN1
GDTC_NCH60_EN1
GDTC_NCH61_EN1
GDTC_NCH62_EN1
GDTC_NCH63_EN1
GDTC_NCH64_EN1
GDTC_NCH65_EN1
GDTC_NCH66_EN1
GDTC_NCH67_EN1
GDTC_NCH68_EN1
GDTC_NCH69_EN1
GDTC_NCH70_EN1
GDTC_NCH71_EN1
GDTC_NCH72_EN1
GDTC_NCH73_EN1
GDTC_NCH74_EN1
GDTC_NCH75_EN1
GDTC_NCH76_EN1
PB_NCH1_PE1
PB_NCH1_PE2
BBGABBBGB
WPB_NCH1_PKGREL_PB1SOLPB_NCH1_PKGREL_SOL1
PB_NCH2_PE1
PB_NCH2_PE2
WPB_NCH2_PKGREL_PB1SOLPB_NCH2_PKGREL_SOL1
PB_NCH3_PE1
PB_NCH3_PE2
WPB_NCH3_PKGREL_PB1SOLPB_NCH3_PKGREL_SOL1
PB_NCH4_PE1
PB_NCH4_PE2
WPB_NCH4_PKGREL_PB1SOLPB_NCH4_PKGREL_SOL1
PB_NCH5_PE1
PB_NCH5_PE2
WPB_NCH5_PKGREL_PB1SOLPB_NCH5_PKGREL_SOL1
PB_NCH6_PE1
PB_NCH6_PE2
WPB_NCH6_PKGREL_PB1SOLPB_NCH6_PKGREL_SOL1
PB_NCH7_PE1
PB_NCH7_PE2
WPB_NCH7_PKGREL_PB1SOLPB_NCH7_PKGREL_SOL1
PB_NCH8_PE1
PB_NCH8_PE2
WPB_NCH8_PKGREL_PB1SOLPB_NCH8_PKGREL_SOL1
PB_NCH9_PE1
PB_NCH9_PE2
WPB_NCH9_PKGREL_PB1SOLPB_NCH9_PKGREL_SOL1
PB_NCH10_PE1
PB_NCH10_PE2
WPB_NCH10_PKGREL_PB1SOLPB_NCH10_PKGREL_SOL1
PB_NCH11_PE1
PB_NCH11_PE2
WPB_NCH11_PKGREL_PB1SOLPB_NCH11_PKGREL_SOL1
PB_NCH12_PE1
PB_NCH12_PE2
WPB_NCH12_PKGREL_PB1SOLPB_NCH12_PKGREL_SOL1
PB_NCH13_PE1
PB_NCH13_PE2
WPB_NCH13_PKGREL_PB1SOLPB_NCH13_PKGREL_SOL1
PB_NCH14_PE1
PB_NCH14_PE2
WPB_NCH14_PKGREL_PB1SOLPB_NCH14_PKGREL_SOL1
PB_NCH15_PE1
PB_NCH15_PE2
WPB_NCH15_PKGREL_PB1SOLPB_NCH15_PKGREL_SOL1
PB_NCH16_PE1
PB_NCH16_PE2
WPB_NCH16_PKGREL_PB1SOLPB_NCH16_PKGREL_SOL1
PB_NCH17_PE1
PB_NCH17_PE2
WPB_NCH17_PKGREL_PB1SOLPB_NCH17_PKGREL_SOL1
PB_NCH18_PE1
PB_NCH18_PE2
WPB_NCH18_PKGREL_PB1SOLPB_NCH18_PKGREL_SOL1
PB_NCH19_PE1
PB_NCH19_PE2
WPB_NCH19_PKGREL_PB1SOLPB_NCH19_PKGREL_SOL1
PB_NCH20_PE1
PB_NCH20_PE2
WPB_NCH20_PKGREL_PB1SOLPB_NCH20_PKGREL_SOL1
PB_NCH21_PE1
PB_NCH21_PE2
WPB_NCH21_PKGREL_PB1SOLPB_NCH21_PKGREL_SOL1
PB_NCH22_PE1
PB_NCH22_PE2
WPB_NCH22_PKGREL_PB1SOLPB_NCH22_PKGREL_SOL1
PB_NCH23_PE1
PB_NCH23_PE2
WPB_NCH23_PKGREL_PB1SOLPB_NCH23_PKGREL_SOL1
PB_NCH24_PE1
PB_NCH24_PE2
WPB_NCH24_PKGREL_PB1SOLPB_NCH24_PKGREL_SOL1
PB_NCH25_PE1
PB_NCH25_PE2
WPB_NCH25_PKGREL_PB1SOLPB_NCH25_PKGREL_SOL1
PB_NCH26_PE1
PB_NCH26_PE2
WPB_NCH26_PKGREL_PB1SOLPB_NCH26_PKGREL_SOL1
PB_NCH27_PE1
PB_NCH27_PE2
WPB_NCH27_PKGREL_PB1SOLPB_NCH27_PKGREL_SOL1
PB_NCH28_PE1
PB_NCH28_PE2
WPB_NCH28_PKGREL_PB1SOLPB_NCH28_PKGREL_SOL1
PB_NCH29_PE1
PB_NCH29_PE2
WPB_NCH29_PKGREL_PB1SOLPB_NCH29_PKGREL_SOL1
PB_NCH30_PE1
PB_NCH30_PE2
WPB_NCH30_PKGREL_PB1SOLPB_NCH30_PKGREL_SOL1
PB_NCH31_PE1
PB_NCH31_PE2
WPB_NCH31_PKGREL_PB1SOLPB_NCH31_PKGREL_SOL1
PB_NCH32_PE1
PB_NCH32_PE2
WPB_NCH32_PKGREL_PB1SOLPB_NCH32_PKGREL_SOL1
PB_NCH33_PE1
PB_NCH33_PE2
WPB_NCH33_PKGREL_PB1SOLPB_NCH33_PKGREL_SOL1
PB_NCH34_PE1
PB_NCH34_PE2
WPB_NCH34_PKGREL_PB1SOLPB_NCH34_PKGREL_SOL1
PB_NCH35_PE1
PB_NCH35_PE2
WPB_NCH35_PKGREL_PB1SOLPB_NCH35_PKGREL_SOL1
PB_NCH36_PE1
PB_NCH36_PE2
WPB_NCH36_PKGREL_PB1SOLPB_NCH36_PKGREL_SOL1
PB_NCH37_PE1
PB_NCH37_PE2
WPB_NCH37_PKGREL_PB1SOLPB_NCH37_PKGREL_SOL1
PB_NCH38_PE1
PB_NCH38_PE2
WPB_NCH38_PKGREL_PB1SOLPB_NCH38_PKGREL_SOL1
PB_NCH39_PE1
PB_NCH39_PE2
WPB_NCH39_PKGREL_PB1SOLPB_NCH39_PKGREL_SOL1
PB_NCH40_PE1
PB_NCH40_PE2
WPB_NCH40_PKGREL_PB1SOLPB_NCH40_PKGREL_SOL1
PB_NCH41_PE1
PB_NCH41_PE2
WPB_NCH41_PKGREL_PB1SOLPB_NCH41_PKGREL_SOL1
PB_NCH42_PE1
PB_NCH42_PE2
WPB_NCH42_PKGREL_PB1SOLPB_NCH42_PKGREL_SOL1
PB_NCH43_PE1
PB_NCH43_PE2
WPB_NCH43_PKGREL_PB1SOLPB_NCH43_PKGREL_SOL1
PB_NCH44_PE1
PB_NCH44_PE2
WPB_NCH44_PKGREL_PB1SOLPB_NCH44_PKGREL_SOL1
PB_NCH45_PE1
PB_NCH45_PE2
WPB_NCH45_PKGREL_PB1SOLPB_NCH45_PKGREL_SOL1
PB_NCH46_PE1
PB_NCH46_PE2
WPB_NCH46_PKGREL_PB1SOLPB_NCH46_PKGREL_SOL1
PB_NCH47_PE1
PB_NCH47_PE2
WPB_NCH47_PKGREL_PB1SOLPB_NCH47_PKGREL_SOL1
PB_NCH48_PE1
PB_NCH48_PE2
WPB_NCH48_PKGREL_PB1SOLPB_NCH48_PKGREL_SOL1
PB_NCH49_PE1
PB_NCH49_PE2
WPB_NCH49_PKGREL_PB1SOLPB_NCH49_PKGREL_SOL1
PB_NCH50_PE1
PB_NCH50_PE2
WPB_NCH50_PKGREL_PB1SOLPB_NCH50_PKGREL_SOL1
PB_NCH51_PE1
PB_NCH51_PE2
WPB_NCH51_PKGREL_PB1SOLPB_NCH51_PKGREL_SOL1
PB_NCH52_PE1
PB_NCH52_PE2
WPB_NCH52_PKGREL_PB1SOLPB_NCH52_PKGREL_SOL1
PB_NCH53_PE1
PB_NCH53_PE2
WPB_NCH53_PKGREL_PB1SOLPB_NCH53_PKGREL_SOL1
PB_NCH54_PE1
PB_NCH54_PE2
WPB_NCH54_PKGREL_PB1SOLPB_NCH54_PKGREL_SOL1
PB_NCH55_PE1
PB_NCH55_PE2
WPB_NCH55_PKGREL_PB1SOLPB_NCH55_PKGREL_SOL1
PB_NCH56_PE1
PB_NCH56_PE2
WPB_NCH56_PKGREL_PB1SOLPB_NCH56_PKGREL_SOL1
PB_NCH57_PE1
PB_NCH57_PE2
WPB_NCH57_PKGREL_PB1SOLPB_NCH57_PKGREL_SOL1
PB_NCH58_PE1
PB_NCH58_PE2
WPB_NCH58_PKGREL_PB1SOLPB_NCH58_PKGREL_SOL1
PB_NCH59_PE1
PB_NCH59_PE2
WPB_NCH59_PKGREL_PB1SOLPB_NCH59_PKGREL_SOL1
PB_NCH60_PE1
PB_NCH60_PE2
WPB_NCH60_PKGREL_PB1SOLPB_NCH60_PKGREL_SOL1
PB_NCH61_PE1
PB_NCH61_PE2
WPB_NCH61_PKGREL_PB1SOLPB_NCH61_PKGREL_SOL1
BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGAB BBGABBBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGB BBGB BBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB BBGBBBGBBBGB

Binary file not shown.

View File

@ -0,0 +1,2 @@
ProjectName: MTN6_Test
RepoURL: http://192.168.5.191:3000/ilia-gurielidze-autstand/MTN6_SCADA.git

@ -0,0 +1 @@
Subproject commit 456de12cca56c09bc1881660b163ac3b5dff593a

View File

@ -1,2 +1,3 @@
Flask
GitPython
pypdf

59
scada_checker.py Normal file
View File

@ -0,0 +1,59 @@
import os
import json
from utils import normalize, get_views_dir_path
def check_scada(project_name, manifest_data):
"""
Checks for aliases in SCADA JSON view files for a specific project.
Updates the 'found_scada' flag in the manifest_data items directly.
"""
if not manifest_data:
print(f"[{project_name}] SCADA Check: No manifest data provided.")
return
views_dir = get_views_dir_path(project_name)
print(f"[{project_name}] Starting SCADA check in directory: {views_dir}...")
found_count = 0
processed_files = 0
if not os.path.exists(views_dir):
print(f"Warning: SCADA Views directory not found at {views_dir}. Skipping SCADA check.")
# No need to mark all as False, they default to False
return
# Create a quick lookup map of normalized_alias -> list of manifest items (handles duplicate aliases)
alias_map = {}
for item in manifest_data:
na = item['normalized_alias']
if na not in alias_map:
alias_map[na] = []
alias_map[na].append(item)
try:
for root, _, files in os.walk(views_dir):
for filename in files:
if filename == 'view.json':
filepath = os.path.join(root, filename)
processed_files += 1
try:
with open(filepath, 'r', encoding='utf-8') as f:
# Read the whole file, normalize it for substring search
# Consider JSON loading for more robust checks? For now, string search.
content = f.read()
normalized_content = normalize(content)
# Check manifest aliases against this file's normalized content
for norm_alias, items in alias_map.items():
# Use 'in' for substring check
if norm_alias and norm_alias in normalized_content:
for item in items:
if not item['found_scada']: # Update only if not already found elsewhere
item['found_scada'] = True
# Count unique aliases found the *first* time
found_count += 1
except Exception as e:
print(f" Warning: Could not read or process JSON file {filepath}: {e}")
except Exception as e:
print(f"Error walking SCADA views directory {views_dir}: {e}")
print(f"[{project_name}] SCADA check finished. Processed {processed_files} view.json files. Found {found_count} unique manifest aliases.")
# No return value needed as manifest_data is modified in-place

51
static/css/style.css Normal file
View File

@ -0,0 +1,51 @@
body { padding: 20px; padding-bottom: 60px; /* Account for status bar */ }
.progress-container, .chart-container {
margin-bottom: 25px;
text-align: center; /* Center chart labels */
}
.chart-label {
font-weight: bold;
margin-bottom: 5px;
display: block;
}
.status-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f8f9fa;
border-top: 1px solid #dee2e6;
padding: 5px 15px;
font-size: 0.9em;
z-index: 1000;
}
/* Style for the overall progress bar - Removed as using Pie chart */
/* Style for panel charts */
.panel-chart-canvas {
max-width: 150px; /* Control pie chart size */
max-height: 150px;
margin: 0 auto; /* Center the canvas */
cursor: pointer; /* Indicate clickable */
}
#scada-panels-progress, #drawing-panels-progress {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Responsive grid */
gap: 20px;
}
.modal-body table { width: 100%; }
.modal-body th, .modal-body td { padding: 5px 10px; border-bottom: 1px solid #eee; vertical-align: middle; }
.modal-body th { background-color: #f8f9fa; text-align: left; }
.status-yes { color: green; font-weight: bold; }
.status-no { color: red; font-weight: bold; }
nav { margin-bottom: 20px; } /* Added for nav spacing */
.chart-container > canvas,
.chart-container > div:not(.processing-indicator) {
transition: opacity 0.3s ease-in-out;
}
.content-hidden-by-loader {
display: none !important; /* Use important to override potential inline styles */
opacity: 0;
}

877
static/js/script.js Normal file
View File

@ -0,0 +1,877 @@
// --- Global State Variables ---
let chartInstancesScada = {}; // Separate instances for SCADA
let chartInstancesDrawing = {}; // Separate instances for Drawing
let currentProjectData = {}; // Stores the LATEST full data received from SSE { project: {status, commit, progress}, ... }
let selectedProjectName = null; // Track the currently selected project
let detailsModalInstance = null;
let currentVisibleSection = 'scada'; // Track visible section: 'scada', 'drawing', 'conflicts'
// --- Chart Configurations ---
const scadaChartLabels = ['Found in SCADA', 'Not Found in SCADA'];
const scadaChartColors = ['rgb(13, 110, 253)', 'rgb(220, 53, 69)'];
const drawingChartLabels = ['Found in Drawing', 'Not Found in Drawing'];
const drawingChartColors = ['rgb(25, 135, 84)', 'rgb(220, 53, 69)'];
// Map backend list keys for modal clicks (can be combined or kept separate if needed)
const scadaListKeysMap = {
found: ['found_both_list', 'found_scada_only_list'],
notFound: ['found_drawing_only_list', 'missing_list']
};
const drawingListKeysMap = {
found: ['found_both_list', 'found_drawing_only_list'],
notFound: ['found_scada_only_list', 'missing_list']
};
// --- Debounce Utility (Only need one) ---
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// --- NEW: Helper Functions for Processing State ---
// Checks if the status message indicates ongoing processing
function isProcessing(statusMsg) {
if (!statusMsg) return false; // Handle undefined/null status
const lowerStatus = statusMsg.toLowerCase();
// Keywords indicating processing (adjust as needed based on app.py status messages)
return lowerStatus.includes('initializ') || // initializing, initial...
lowerStatus.includes('cloning') ||
lowerStatus.includes('fetching') ||
lowerStatus.includes('pulling') ||
lowerStatus.includes('checking') || // checking repo, checking scada, checking drawings
lowerStatus.includes('reading manifest') ||
lowerStatus.includes('calculating') ||
lowerStatus.includes('extracting') || // If PDF extraction status is sent
lowerStatus.includes('loading data'); // From handleProjectChange initial state
}
// Displays a loading indicator in a container element
function showLoadingIndicator(containerElement, message = "Processing project data...") {
if (!containerElement) return;
// Hide existing content
for (const child of containerElement.children) {
if (!child.classList.contains('processing-indicator')) {
child.classList.add('content-hidden-by-loader');
}
}
let indicatorDiv = containerElement.querySelector('.processing-indicator');
if (!indicatorDiv) {
indicatorDiv = document.createElement('div');
indicatorDiv.className = 'text-center p-4 processing-indicator';
indicatorDiv.innerHTML = `
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 fst-italic"></p>
`;
// Prepend to ensure it's visible even if container had position: relative children
containerElement.prepend(indicatorDiv);
}
// Update message
const messageElement = indicatorDiv.querySelector('p');
if (messageElement) {
// Sanitize message slightly before displaying
const safeMessage = message.replace(/</g, "&lt;").replace(/>/g, "&gt;");
messageElement.textContent = safeMessage;
}
indicatorDiv.style.display = 'block'; // Ensure indicator is visible
}
// Removes the loading indicator from a container
function clearLoadingIndicator(containerElement) {
if (!containerElement) return;
// Remove indicator
const indicatorDiv = containerElement.querySelector('.processing-indicator');
if (indicatorDiv) {
// indicatorDiv.remove(); // Or hide it if preferred
indicatorDiv.style.display = 'none';
}
// Show original content
for (const child of containerElement.children) {
child.classList.remove('content-hidden-by-loader');
}
// Remove default "Loading panel data..." placeholders if they still exist
const placeholderP = Array.from(containerElement.querySelectorAll('p.fst-italic')).find(p =>
p.textContent.toLowerCase().includes('loading') &&
!p.closest('.processing-indicator')
);
if (placeholderP) {
placeholderP.remove();
}
}
// Sets the entire UI to reflect a processing state
function showProcessingStateUI(projectData) {
const statusMsg = projectData ? projectData.status : "Loading data...";
const containers = [
document.getElementById('overall-scada-progress'),
document.getElementById('scada-panels-progress'),
document.getElementById('overall-drawing-progress'),
document.getElementById('drawing-panels-progress'),
document.getElementById('panels-conflicts')
];
console.log(`[UI State] Setting processing state: "${statusMsg}"`);
// Destroy all existing charts immediately to prevent rendering issues
Object.values(chartInstancesScada).forEach(chart => chart?.destroy());
chartInstancesScada = {};
Object.values(chartInstancesDrawing).forEach(chart => chart?.destroy());
chartInstancesDrawing = {};
// Show loading indicator in all content containers
containers.forEach(container => {
if (container) {
showLoadingIndicator(container, statusMsg); // Show the actual status message
}
});
// Clear text content that is updated directly (like overall percentages)
const overallScadaText = document.getElementById('overall-scada-text');
if (overallScadaText) overallScadaText.textContent = '';
const overallDrawingText = document.getElementById('overall-drawing-text');
if (overallDrawingText) overallDrawingText.textContent = '';
const conflictCount = document.getElementById('conflict-count');
if (conflictCount) {
conflictCount.textContent = '...';
conflictCount.style.display = 'inline-block'; // Show it while loading
}
// Ensure the correct section's container is visible if it was hidden
// (showSection handles visibility, but this prevents blank screens if called directly)
const scadaContent = document.getElementById('scada-content');
const drawingsContent = document.getElementById('drawings-content');
const conflictsContent = document.getElementById('conflicts-content');
if (scadaContent && currentVisibleSection === 'scada') scadaContent.style.display = 'block';
if (drawingsContent && currentVisibleSection === 'drawings') drawingsContent.style.display = 'block';
if (conflictsContent && currentVisibleSection === 'conflicts') conflictsContent.style.display = 'block';
}
// Clears loading indicators and triggers the actual UI rendering
function showReadyStateUI(projectData) {
console.log(`[UI State] Setting ready state for project: ${selectedProjectName}`);
const containers = [
document.getElementById('overall-scada-progress'),
document.getElementById('scada-panels-progress'),
document.getElementById('overall-drawing-progress'),
document.getElementById('drawing-panels-progress'),
document.getElementById('panels-conflicts')
];
// Clear loading indicators from all containers
containers.forEach(container => {
if(container) clearLoadingIndicator(container);
});
// Call core update functions (wrapped in setTimeout for smooth rendering)
console.log(`Project state is ready. Queueing core redraw.`);
setTimeout(() => {
console.log(`Passing project data to UI updates:`, projectData);
updateUIScadaCore(projectData);
updateUIDrawingCore(projectData);
updateUIConflictsCore(projectData);
}, 0);
}
// --- Chart Click Handler (Needs PROJECT CONTEXT) ---
function handleChartClick(event, elements, chart, context) {
if (elements.length > 0 && selectedProjectName) { // Check if a project is selected
const clickedElementIndex = elements[0].index;
const isOverallChart = chart.canvas.id.startsWith('overall-');
const identifier = isOverallChart ? '__overall__' : chart.canvas.id.replace(`chart-${context}-`, '');
const categoryType = clickedElementIndex === 0 ? 'found' : 'notFound';
// Pass selectedProjectName to the modal function
showDetailsModal(selectedProjectName, identifier, categoryType, context);
}
}
// --- Core UI Update Functions (Need selected project data) ---
function updateUIScadaCore(projectData) { // Accepts data for the selected project
console.log(`Running core SCADA UI redraw logic for project: ${selectedProjectName}`);
const progressDetails = (projectData && projectData.progress) ? projectData.progress : { overall: {}, panels: {} };
const overallData = progressDetails.overall || {};
const overallTotal = overallData.total_csv || 0;
const overallFoundScada = (overallData.found_both || 0) + (overallData.found_scada_only || 0);
const overallNotFoundScada = (overallData.found_drawing_only || 0) + (overallData.missing_both || 0);
const overallPercentageFound = overallTotal > 0 ? ((overallFoundScada / overallTotal) * 100).toFixed(1) : 0;
const overallChartCounts = [overallFoundScada, overallNotFoundScada];
// Update project name display in headers
document.querySelectorAll('.project-name-display').forEach(el => el.textContent = selectedProjectName || '...');
const overallScadaTextElement = document.getElementById('overall-scada-text');
if (overallScadaTextElement) {
overallScadaTextElement.textContent = `Found in SCADA: ${overallFoundScada}/${overallTotal} (${overallPercentageFound}%)`;
} else {
console.warn("Element with ID 'overall-scada-text' not found when trying to update SCADA text.");
}
const isSectionVisible = (currentVisibleSection === 'scada');
if (isSectionVisible) {
const overallScadaCanvas = document.getElementById('overall-scada-chart-canvas');
if (chartInstancesScada['overall']) {
if (JSON.stringify(chartInstancesScada['overall'].data.datasets[0].data) !== JSON.stringify(overallChartCounts)) {
chartInstancesScada['overall'].data.datasets[0].data = overallChartCounts;
chartInstancesScada['overall'].update('none');
}
} else if (overallScadaCanvas) {
console.log("Creating overall SCADA chart (visible).");
const ctxOverall = overallScadaCanvas.getContext('2d');
// Pass selectedProjectName to identify data context for clicks/tooltips
chartInstancesScada['overall'] = new Chart(ctxOverall, createChartConfig(overallChartCounts, overallTotal, 'scada', 'overall', selectedProjectName));
}
} else {
if (chartInstancesScada['overall']) {
console.log("Destroying hidden overall SCADA chart.");
chartInstancesScada['overall'].destroy();
delete chartInstancesScada['overall'];
}
}
const panelsContainer = document.getElementById('scada-panels-progress');
const panelsData = progressDetails.panels || {};
updatePanelCharts(panelsContainer, panelsData, chartInstancesScada, 'scada');
console.log("Finished SCADA UI core redraw.");
}
function updateUIDrawingCore(projectData) { // Accepts data for the selected project
console.log(`Running core Drawing UI redraw logic for project: ${selectedProjectName}`);
const progressDetails = (projectData && projectData.progress) ? projectData.progress : { overall: {}, panels: {} };
const overallData = progressDetails.overall || {};
const overallTotal = overallData.total_csv || 0;
const overallFoundDrawing = (overallData.found_both || 0) + (overallData.found_drawing_only || 0);
const overallNotFoundDrawing = (overallData.found_scada_only || 0) + (overallData.missing_both || 0);
const overallPercentageFound = overallTotal > 0 ? ((overallFoundDrawing / overallTotal) * 100).toFixed(1) : 0;
const overallChartCounts = [overallFoundDrawing, overallNotFoundDrawing];
document.querySelectorAll('.project-name-display').forEach(el => el.textContent = selectedProjectName || '...');
const overallDrawingTextElement = document.getElementById('overall-drawing-text');
if (overallDrawingTextElement) {
overallDrawingTextElement.textContent = `Found in Drawing: ${overallFoundDrawing}/${overallTotal} (${overallPercentageFound}%)`;
} else {
console.warn("Element with ID 'overall-drawing-text' not found when trying to update Drawing text.");
}
const isSectionVisible = (currentVisibleSection === 'drawings');
if (isSectionVisible) {
const overallDrawingCanvas = document.getElementById('overall-drawing-chart-canvas');
if (chartInstancesDrawing['overall']) {
if (JSON.stringify(chartInstancesDrawing['overall'].data.datasets[0].data) !== JSON.stringify(overallChartCounts)) {
chartInstancesDrawing['overall'].data.datasets[0].data = overallChartCounts;
chartInstancesDrawing['overall'].update('none');
}
} else if (overallDrawingCanvas) {
console.log("Creating overall drawing chart (visible).");
const ctxOverall = overallDrawingCanvas.getContext('2d');
chartInstancesDrawing['overall'] = new Chart(ctxOverall, createChartConfig(overallChartCounts, overallTotal, 'drawing', 'overall', selectedProjectName));
}
} else {
if (chartInstancesDrawing['overall']) {
console.log("Destroying hidden overall Drawing chart.");
chartInstancesDrawing['overall'].destroy();
delete chartInstancesDrawing['overall'];
}
}
const panelsContainer = document.getElementById('drawing-panels-progress');
const panelsData = progressDetails.panels || {};
updatePanelCharts(panelsContainer, panelsData, chartInstancesDrawing, 'drawings');
console.log("Finished Drawing UI core redraw.");
}
function updateUIConflictsCore(projectData) { // Accepts data for the selected project
console.log(`Running core Conflicts UI redraw logic for project: ${selectedProjectName}`);
const progressDetails = (projectData && projectData.progress) ? projectData.progress : { overall: {}, panels: {} };
const panelsContainer = document.getElementById('panels-conflicts');
panelsContainer.innerHTML = '';
document.querySelectorAll('.project-name-display').forEach(el => el.textContent = selectedProjectName || '...');
const panelsData = progressDetails.panels || {};
let totalConflicts = 0;
let panelsWithConflicts = 0;
if (!panelsData || Object.keys(panelsData).length === 0) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No panel data available yet.</p>';
} else {
const sortedPanels = Object.keys(panelsData).sort();
sortedPanels.forEach(panelName => {
const panel = panelsData[panelName];
const conflictsList = panel.found_scada_only_list || [];
if (conflictsList.length > 0) {
panelsWithConflicts++;
totalConflicts += conflictsList.length;
// ... (Create header and table as in conflicts.html) ...
const panelHeader = document.createElement('h4');
panelHeader.className = 'mt-4 mb-2';
panelHeader.textContent = `${panelName} (${conflictsList.length} conflicts)`;
panelsContainer.appendChild(panelHeader);
const table = document.createElement('table');
table.className = 'table table-sm table-striped table-hover table-bordered';
const thead = table.createTHead();
thead.innerHTML = `<tr><th>Alias</th><th>Panel</th><th>SCADA Status</th><th>Drawing Status</th><th>Equipment Type</th><th>Type of Conveyor</th></tr>`;
const tbody = table.createTBody();
conflictsList.sort((a, b) => a.alias.localeCompare(b.alias)).forEach(item => {
const row = tbody.insertRow();
row.classList.add('table-warning');
row.insertCell().textContent = item.alias;
row.insertCell().textContent = item.control_panel;
row.insertCell().innerHTML = '<span class="status-yes">Yes</span>';
row.insertCell().innerHTML = '<span class="status-no">No</span>';
row.insertCell().textContent = item.equipment_type || 'N/A';
row.insertCell().textContent = item.conveyor_type || 'N/A';
});
panelsContainer.appendChild(table);
}
});
if (panelsWithConflicts === 0) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No conflicts found across all panels.</p>';
}
}
// Update total count badge
const countBadge = document.getElementById('conflict-count');
if (countBadge) {
countBadge.textContent = totalConflicts;
countBadge.style.display = totalConflicts > 0 ? 'inline-block' : 'none';
}
console.log("Finished Conflicts UI core redraw.");
}
// --- Generic Panel Chart Update Logic ---
function updatePanelCharts(panelsContainer, panelsData, chartInstances, context) { // context: 'scada' or 'drawing'
const incomingPanelNames = new Set(Object.keys(panelsData).sort());
const existingInstanceNames = new Set(Object.keys(chartInstances).filter(k => k !== 'overall'));
// --- Check if the context matches the currently visible section ---
const isSectionVisible = (context === currentVisibleSection);
if (!isSectionVisible) {
// If section is not visible, destroy existing panel chart instances for this context
console.log(`Destroying hidden panel charts for context: ${context}`);
existingInstanceNames.forEach(panelName => {
if (chartInstances[panelName]) {
chartInstances[panelName].destroy();
delete chartInstances[panelName];
}
});
// Don't proceed further if the section is hidden
return;
}
if (incomingPanelNames.size > 0) {
const loadingMsg = panelsContainer.querySelector('p');
if (loadingMsg) { loadingMsg.remove(); }
incomingPanelNames.forEach(panelName => {
const panel = panelsData[panelName];
const panelTotal = (panel && panel.total) || 0;
let panelChartCounts = [0, 0]; // Default to [0, 0]
if (panel) { // Only calculate if panel data exists
if (context === 'scada') {
panelChartCounts = [(panel.found_both || 0) + (panel.found_scada_only || 0), (panel.found_drawing_only || 0) + (panel.missing_both || 0)];
} else { // drawing
panelChartCounts = [(panel.found_both || 0) + (panel.found_drawing_only || 0), (panel.found_scada_only || 0) + (panel.missing_both || 0)];
}
}
// --- Only update/create chart if section is visible ---
if (isSectionVisible) {
if (chartInstances[panelName]) {
if (JSON.stringify(chartInstances[panelName].data.datasets[0].data) !== JSON.stringify(panelChartCounts)) {
chartInstances[panelName].data.datasets[0].data = panelChartCounts;
chartInstances[panelName].update('none');
}
} else {
let canvas = document.getElementById(`chart-${context}-${panelName}`); // Use context in ID
if (canvas) {
console.log(`Recreating ${context} chart instance for panel (visible): ${panelName}`);
const ctx = canvas.getContext('2d');
chartInstances[panelName] = new Chart(ctx, createChartConfig(panelChartCounts, panelTotal, context, panelName, selectedProjectName));
} else {
console.log(`Creating new ${context} panel elements and chart (visible) for: ${panelName}`);
const chartContainer = document.createElement('div');
chartContainer.id = `chart-container-${context}-${panelName}`; // Use context in ID
chartContainer.className = 'chart-container';
const label = document.createElement('span');
label.className = 'chart-label'; label.textContent = panelName;
canvas = document.createElement('canvas'); // Reassign canvas variable
canvas.id = `chart-${context}-${panelName}`; // Use context in ID
canvas.className = 'panel-chart-canvas';
chartContainer.appendChild(label);
chartContainer.appendChild(canvas);
// Added Log before append
console.log(`[updatePanelCharts] Appending chartContainer (${chartContainer.id}) to panelsContainer (${panelsContainer ? panelsContainer.id : 'null'})`);
panelsContainer.appendChild(chartContainer); // Append to the main panels progress div
const ctx = canvas.getContext('2d');
chartInstances[panelName] = new Chart(ctx, createChartConfig(panelChartCounts, panelTotal, context, panelName, selectedProjectName));
}
}
}
// --- End visibility check ---
});
} else {
if (!panelsContainer.querySelector('p')) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No panel data available yet.</p>';
}
}
existingInstanceNames.forEach(panelName => {
if (!incomingPanelNames.has(panelName)) {
console.log(`Removing ${context} panel elements and chart for: ${panelName}`);
// Ensure chart is destroyed before removing element
if (chartInstances[panelName]) {
chartInstances[panelName].destroy();
delete chartInstances[panelName];
}
const chartElement = document.getElementById(`chart-container-${context}-${panelName}`); // Use context
if (chartElement) {
chartElement.remove();
}
}
});
}
// --- Generic Helper to create chart config --- Needs PROJECT context ---
function createChartConfig(chartCounts, total, context, identifier, projectName) { // Added projectName
const labels = context === 'scada' ? scadaChartLabels : drawingChartLabels;
const colors = context === 'scada' ? scadaChartColors : drawingChartColors;
const datasetLabel = context === 'scada' ? 'SCADA Match' : 'Drawing Match';
// Retrieve the correct project's progress data for tooltip calculation
const projectProgress = (currentProjectData[projectName] && currentProjectData[projectName].progress) ? currentProjectData[projectName].progress : {};
return {
type: 'pie',
data: {
labels: labels,
datasets: [{
label: datasetLabel,
data: chartCounts,
backgroundColor: colors,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
onClick: (event, elements, chart) => handleChartClick(event, elements, chart, context), // Pass context
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(ctxTooltip) {
let label = ctxTooltip.label || '';
if (label) label += ': ';
const value = ctxTooltip.parsed;
if (value !== null) label += value;
// Workaround: Use total passed to function for panel charts, access stored data for overall
const chartTotal = (identifier === 'overall' && projectProgress.overall)
? projectProgress.overall.total_csv
: total; // Use the 'total' passed in for panel charts
if (chartTotal && chartTotal > 0 && value !== null) { // Add null check for value
label += ` (${((value / chartTotal) * 100).toFixed(1)}%)`;
}
return label;
}
}
}
}
}
};
}
// --- Process Update: Extracts data for selected project ---
function processUpdate(fullData) {
console.log("SSE Received Full Data:", fullData); // Log the raw data
// Store the latest full data
currentProjectData = {}; // Reset first
fullData.projects.forEach(projName => {
currentProjectData[projName] = {
status: fullData.status ? fullData.status[projName] : 'Unknown',
last_commit: fullData.last_commit ? fullData.last_commit[projName] : 'N/A',
progress: fullData.progress ? fullData.progress[projName] : { overall: {}, panels: {} }
};
});
// Get current selection AFTER storing data
selectedProjectName = document.getElementById('projectSelector').value;
console.log(`Selected project: ${selectedProjectName}`);
if (!selectedProjectName || !currentProjectData[selectedProjectName]) {
console.log("No project selected or no data for selected project. UI updates skipped.");
// Optionally clear the UI or show a message
updateStatusBar('N/A', 'No project selected or no data');
return;
}
// Extract data for the selected project
const projectData = currentProjectData[selectedProjectName];
const currentCommit = projectData.last_commit;
// Update status bar immediately for the selected project
updateStatusBar(selectedProjectName, projectData.status, projectData.last_commit);
// Check the processing state and update UI accordingly
if (isProcessing(projectData.status)) {
console.log(`Project ${selectedProjectName} is processing. Showing loading state.`);
showProcessingStateUI(projectData);
} else {
console.log(`Project ${selectedProjectName} is ready/error. Showing final state.`);
// TODO: Add check here if commit hash changed? Or just always update?
// For now, always update the UI if the state is not 'processing'.
showReadyStateUI(projectData);
}
}
// --- Debounced version of the processing function ---
const debouncedProcessUpdate = debounce(processUpdate, 250); // Single debouncer
// --- Modal Display Function (Needs PROJECT context) ---
function showDetailsModal(projectName, identifier, categoryType, context) { // Added projectName
let sourceData = null;
let panelNameDisplay = "";
const listKeysMap = context === 'scada' ? scadaListKeysMap : drawingListKeysMap;
const listTypeLabel = categoryType === 'found'
? (context === 'scada' ? 'Found in SCADA' : 'Found in Drawing')
: (context === 'scada' ? 'Not Found in SCADA' : 'Not Found in Drawing');
// Get the specific project's progress data
const projectProgress = (currentProjectData[projectName] && currentProjectData[projectName].progress) ? currentProjectData[projectName].progress : {};
if (identifier === '__overall__') {
sourceData = projectProgress.overall || null;
panelNameDisplay = `Overall (${projectName})`;
} else {
sourceData = (projectProgress.panels) ? projectProgress.panels[identifier] : null;
panelNameDisplay = `${identifier} (${projectName})`;
}
if (!sourceData) {
console.error(`Could not find source data for modal. Project: ${projectName}, Identifier: ${identifier}, Context: ${context}`);
alert("Error: Could not load details data.");
return;
}
const backendListKeys = listKeysMap[categoryType];
if (!backendListKeys) { /* ... error handling ... */ return; }
let combinedDataList = [];
backendListKeys.forEach(key => {
if (sourceData[key]) {
combinedDataList = combinedDataList.concat(sourceData[key]);
}
});
if (combinedDataList.length === 0) { /* ... alert handling ... */ return; }
const modalTitleElement = document.getElementById('detailsModalLabel');
const modalTableBody = document.querySelector('#detailsModal .modal-body tbody');
modalTitleElement.innerHTML = `${listTypeLabel} Items for ${panelNameDisplay} <span class="badge bg-secondary ms-2">${combinedDataList.length}</span>`;
modalTableBody.innerHTML = '';
combinedDataList.sort((a, b) => a.alias.localeCompare(b.alias)).forEach(item => {
const row = document.createElement('tr');
row.insertCell().textContent = item.alias;
row.insertCell().textContent = item.control_panel;
const scadaCell = row.insertCell(); scadaCell.innerHTML = item.found_scada ? '<span class="status-yes">Yes</span>' : '<span class="status-no">No</span>';
const drawingCell = row.insertCell(); drawingCell.innerHTML = item.found_drawing ? '<span class="status-yes">Yes</span>' : '<span class="status-no">No</span>';
row.insertCell().textContent = item.equipment_type || 'N/A';
row.insertCell().textContent = item.conveyor_type || 'N/A';
if (item.found_scada && !item.found_drawing) { row.classList.add('table-warning'); }
modalTableBody.appendChild(row);
});
if (!detailsModalInstance) {
detailsModalInstance = new bootstrap.Modal(document.getElementById('detailsModal'));
}
detailsModalInstance.show();
}
// --- Update Status Bar Helper ---
function updateStatusBar(projectName, statusMsg, commitHash) {
document.getElementById('selected-project-status-name').textContent = projectName || '...';
document.getElementById('status-message').textContent = statusMsg || 'N/A';
document.getElementById('last-commit').textContent = commitHash || 'N/A';
}
// --- Navigation Handling ---
function showSection(sectionId) {
console.log("Showing section:", sectionId);
document.getElementById('scada-content').style.display = 'none';
document.getElementById('drawings-content').style.display = 'none';
document.getElementById('conflicts-content').style.display = 'none';
const elementToShow = document.getElementById(`${sectionId}-content`);
if (elementToShow) {
elementToShow.style.display = 'block';
currentVisibleSection = sectionId;
// --- Update content based on current project state ---
const projectData = currentProjectData[selectedProjectName];
if (projectData) {
const statusMsg = projectData.status;
console.log(`[ShowSection] Updating visible section ${sectionId} for project ${selectedProjectName}. Status: ${statusMsg}`);
if (isProcessing(statusMsg)) {
// Project is processing, ensure loading indicator is shown in the relevant containers for this section
console.log(`[ShowSection] Project processing, showing loading indicator for ${sectionId}.`);
if (sectionId === 'scada') {
showLoadingIndicator(document.getElementById('overall-scada-progress'), statusMsg);
showLoadingIndicator(document.getElementById('scada-panels-progress'), statusMsg);
} else if (sectionId === 'drawings') {
showLoadingIndicator(document.getElementById('overall-drawing-progress'), statusMsg);
showLoadingIndicator(document.getElementById('drawing-panels-progress'), statusMsg);
} else if (sectionId === 'conflicts') {
showLoadingIndicator(document.getElementById('panels-conflicts'), statusMsg);
}
// Destroy any charts that might have been left over (belt and braces)
if (sectionId === 'scada') {
Object.values(chartInstancesScada).forEach(chart => chart?.destroy());
chartInstancesScada = {};
} else if (sectionId === 'drawings') {
Object.values(chartInstancesDrawing).forEach(chart => chart?.destroy());
chartInstancesDrawing = {};
}
} else {
// Project is ready, trigger the specific update function for the visible section
console.log(`[ShowSection] Project ready, calling update function for ${sectionId}.`);
// Use setTimeout to ensure DOM update (display: block) is processed first
setTimeout(() => {
// Re-fetch projectData in case it changed slightly between checks
const currentData = currentProjectData[selectedProjectName];
if (currentData && !isProcessing(currentData.status)) { // Double check status
if (sectionId === 'scada') {
updateUIScadaCore(currentData);
} else if (sectionId === 'drawings') {
updateUIDrawingCore(currentData);
} else if (sectionId === 'conflicts') {
updateUIConflictsCore(currentData);
}
} else {
console.log(`[ShowSection] Status changed to processing before UI update could run for ${sectionId}.`)
// If it became processing again, show indicator
showProcessingStateUI(currentData);
}
}, 0); // Delay slightly
}
} else {
console.log(`[ShowSection] Section ${sectionId} shown, but no data currently available for project ${selectedProjectName}.`);
// Show loading indicator in the visible section as data is missing
const msg = "Loading data...";
if (sectionId === 'scada') { showLoadingIndicator(document.getElementById('overall-scada-progress'), msg); showLoadingIndicator(document.getElementById('scada-panels-progress'), msg); }
else if (sectionId === 'drawings') { showLoadingIndicator(document.getElementById('overall-drawing-progress'), msg); showLoadingIndicator(document.getElementById('drawing-panels-progress'), msg); }
else if (sectionId === 'conflicts') { showLoadingIndicator(document.getElementById('panels-conflicts'), msg); }
}
// --- End section update trigger ---
} else {
console.error("Attempted to show unknown section:", sectionId);
document.getElementById('scada-content').style.display = 'block'; // Default back to SCADA
currentVisibleSection = 'scada';
}
// Update active nav link
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
// Match link's data-view attribute to sectionId
if (link.getAttribute('data-view') === sectionId) {
link.classList.add('active');
}
});
}
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM Loaded, setting up navigation and project selector...");
const projectSelector = document.getElementById('projectSelector');
if(projectSelector) {
// Set initial selection based on first option (or potentially embedded initial data)
selectedProjectName = projectSelector.value;
projectSelector.addEventListener('change', handleProjectChange);
console.log(`Initial project selected: ${selectedProjectName}`);
// Update initial status bar text
const initialStatus = (initialServerData && initialServerData.status && initialServerData.status[selectedProjectName])
? initialServerData.status[selectedProjectName]
: 'Initializing...';
updateStatusBar(selectedProjectName, initialStatus, 'N/A');
}
document.querySelectorAll('.nav-link').forEach(link => {
// Get the target section directly from the data-view attribute
const targetSection = link.getAttribute('data-view');
if (targetSection) { // Ensure the attribute exists
link.addEventListener('click', (event) => {
event.preventDefault(); // Prevent page reload
// Use the targetSection directly when calling showSection
showSection(targetSection);
});
} else {
console.warn("Nav link found without data-view attribute:", link);
}
});
// Show initial section (SCADA by default)
showSection('scada');
setupAddProjectForm(); // Call the setup function for the new form
});
// --- Connect to SSE stream (Single connection) ---
console.log("Initializing SSE connection...");
const eventSource = new EventSource("/stream");
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
debouncedProcessUpdate(data); // Call the single debounced processor
} catch (error) {
console.error("Error parsing SSE data:", error);
document.getElementById('status-message').textContent = 'Error processing update from server.';
}
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
document.getElementById('status-message').textContent = 'Connection to server lost. Retrying...';
};
console.log("SSE handler set up.");
// --- Project Selector Change Handler ---
function handleProjectChange() {
selectedProjectName = document.getElementById('projectSelector').value;
console.log(`Project selection changed to: ${selectedProjectName}`);
// Immediately update status bar for the selected project using stored data
const projectData = currentProjectData[selectedProjectName];
if (projectData) {
console.log(`[Project Change] Data found for ${selectedProjectName}. Status: ${projectData.status}`);
updateStatusBar(selectedProjectName, projectData.status, projectData.last_commit);
// Update UI based on the current state of the selected project
if (isProcessing(projectData.status)) {
showProcessingStateUI(projectData);
} else {
// Trigger a UI redraw using the stored data for the newly selected project
console.log(`[Project Change] Triggering redraw for newly selected project: ${selectedProjectName}`);
showReadyStateUI(projectData); // Use the new function
}
} else {
// Handle case where data might not be available yet for the selected project
const loadingStatus = 'Loading data...';
console.log(`[Project Change] No data found yet for selected project: ${selectedProjectName}. Showing loading state.`);
updateStatusBar(selectedProjectName, loadingStatus, 'N/A');
// Show processing/loading indicators in all sections
showProcessingStateUI(null); // Pass null to show generic loading message
}
}
// --- Initialize Add Project Form ---
function setupAddProjectForm() {
const form = document.getElementById('addProjectForm');
const statusDiv = document.getElementById('addProjectStatus');
const submitButton = form.querySelector('button[type="submit"]');
if (!form) {
console.log("Add Project form not found on this page.");
return; // Exit if the form isn't present
}
form.addEventListener('submit', async (event) => {
event.preventDefault(); // Prevent default HTML form submission
statusDiv.style.display = 'none';
statusDiv.textContent = '';
statusDiv.className = 'mt-3 alert'; // Reset classes
submitButton.disabled = true;
statusDiv.classList.add('alert-info');
statusDiv.textContent = 'Uploading project data...';
statusDiv.style.display = 'block';
const formData = new FormData(form);
const projectNameInput = document.getElementById('projectName');
// Basic validation for project name (allow letters, numbers, underscore, hyphen)
const projectName = projectNameInput.value.trim();
if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
statusDiv.classList.remove('alert-info');
statusDiv.classList.add('alert-danger');
statusDiv.textContent = 'Invalid Project Name. Use only letters, numbers, underscores, or hyphens.';
statusDiv.style.display = 'block';
submitButton.disabled = false;
return;
}
// Additional Client-Side Validation (Optional but recommended)
const manifestFile = document.getElementById('manifestFile').files[0];
const pdfFiles = document.getElementById('pdfFiles').files;
if (!manifestFile) {
statusDiv.classList.remove('alert-info');
statusDiv.classList.add('alert-danger');
statusDiv.textContent = 'Manifest CSV file is required.';
statusDiv.style.display = 'block';
submitButton.disabled = false;
return;
}
if (pdfFiles.length === 0) {
statusDiv.classList.remove('alert-info');
statusDiv.classList.add('alert-danger');
statusDiv.textContent = 'At least one Drawing PDF file is required.';
statusDiv.style.display = 'block';
submitButton.disabled = false;
return;
}
try {
const response = await fetch('/add_project', {
method: 'POST',
body: formData // FormData handles multipart/form-data encoding
});
const result = await response.json();
statusDiv.classList.remove('alert-info');
if (response.ok && result.success) {
statusDiv.classList.add('alert-success');
statusDiv.textContent = result.message + ' Please restart the server for the new project to appear.';
form.reset(); // Clear the form on success
// Keep button disabled after successful submission
} else {
statusDiv.classList.add('alert-danger');
statusDiv.textContent = 'Error: ' + (result.message || 'Unknown error occurred.');
submitButton.disabled = false; // Re-enable button on error
}
} catch (error) {
console.error('Error submitting add project form:', error);
statusDiv.classList.remove('alert-info');
statusDiv.classList.add('alert-danger');
statusDiv.textContent = 'Network error or server unavailable. Please try again.';
submitButton.disabled = false; // Re-enable button on network error
}
statusDiv.style.display = 'block'; // Ensure status is visible
});
}

View File

@ -3,118 +3,103 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SCADA Progress Monitor</title>
<title>Multi-Project Progress Monitor</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { padding: 20px; padding-bottom: 60px; /* Account for status bar */ }
.progress-container, .chart-container {
margin-bottom: 25px;
text-align: center; /* Center chart labels */
}
.chart-label {
font-weight: bold;
margin-bottom: 5px;
display: block;
}
.status-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f8f9fa;
border-top: 1px solid #dee2e6;
padding: 5px 15px;
font-size: 0.9em;
z-index: 1000;
}
/* Style for the overall progress bar - Removed as using Pie chart */
/* Style for panel charts */
.panel-chart-canvas {
max-width: 150px; /* Control pie chart size */
max-height: 150px;
margin: 0 auto; /* Center the canvas */
cursor: pointer; /* Indicate clickable */
}
#scada-panels-progress, #drawing-panels-progress {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Responsive grid */
gap: 20px;
}
.modal-body table { width: 100%; }
.modal-body th, .modal-body td { padding: 5px 10px; border-bottom: 1px solid #eee; vertical-align: middle; }
.modal-body th { background-color: #f8f9fa; text-align: left; }
.status-yes { color: green; font-weight: bold; }
.status-no { color: red; font-weight: bold; }
nav { margin-bottom: 20px; } /* Added for nav spacing */
</style>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="container">
<!-- Added Navigation -->
<nav class="nav nav-pills">
<a class="nav-link active" aria-current="page" href="/">SCADA Progress</a>
<a class="nav-link" href="/drawings">Drawing Progress</a>
<a class="nav-link" href="/conflicts">Conflicts</a>
<!-- Project Selector -->
<div class="row mb-3 align-items-center">
<div class="col-auto">
<label for="projectSelector" class="col-form-label">Select Project:</label>
</div>
<div class="col">
<select class="form-select" id="projectSelector">
{% if projects %}
{% for project in projects %}
<option value="{{ project }}">{{ project }}</option>
{% endfor %}
{% else %}
<option value="" disabled>No projects found</option>
{% endif %}
</select>
</div>
<!-- Add Project Button -->
<div class="col-auto">
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addProjectModal">
Add Project
</button>
</div>
</div>
<!-- Navigation between views (remains the same) -->
<nav class="nav nav-pills mb-3" id="viewTabs">
<a class="nav-link active" aria-current="page" href="#" data-view="scada">SCADA Progress</a>
<a class="nav-link" href="#" data-view="drawings">Drawing Progress</a>
<a class="nav-link" href="#" data-view="conflicts">Conflicts</a>
</nav>
<!-- SCADA Content Section -->
<div id="scada-content">
<h1 class="mb-4">SCADA Device Placement Progress</h1>
<p>Compares the Equipment Manifest against the SCADA view.json files.</p>
<!-- Dynamic Content Area -->
<div id="project-content">
<!-- SCADA Content Section -->
<div id="scada-content" class="view-content">
<h1 class="mb-4">SCADA Device Placement Progress (<span class="project-name-display"></span>)</h1>
<p>Compares the Equipment Manifest against the SCADA view.json files.</p>
<div id="overall-scada-progress" class="chart-container">
<span class="chart-label">Overall SCADA Progress</span>
<canvas id="overall-scada-chart-canvas" class="panel-chart-canvas" style="max-width: 200px; max-height: 200px;"></canvas>
<div id="overall-scada-text" style="font-weight: bold; margin-top: 10px;">Found in SCADA: 0/0 (0%)</div>
<div id="overall-scada-progress" class="chart-container">
<span class="chart-label">Overall SCADA Progress</span>
<canvas id="overall-scada-chart-canvas" class="panel-chart-canvas" style="max-width: 200px; max-height: 200px;"></canvas>
<div id="overall-scada-text" style="font-weight: bold; margin-top: 10px;">Found in SCADA: 0/0 (0%)</div>
</div>
<hr>
<h2>SCADA Progress by Control Panel</h2>
<div id="scada-panels-progress">
<p>Loading panel data...</p>
</div>
</div>
<hr>
<!-- Drawing Content Section (Initially Hidden) -->
<div id="drawings-content" class="view-content" style="display: none;">
<h1 class="mb-4">Drawing Device Placement Progress (<span class="project-name-display"></span>)</h1>
<p>Compares the Equipment Manifest against the extracted text from drawing files (.txt).</p>
<h2>SCADA Progress by Control Panel</h2>
<div id="scada-panels-progress">
<p>Loading panel data...</p>
</div>
</div>
<div id="overall-drawing-progress" class="chart-container">
<span class="chart-label">Overall Drawing Progress</span>
<canvas id="overall-drawing-chart-canvas" class="panel-chart-canvas" style="max-width: 200px; max-height: 200px;"></canvas>
<div id="overall-drawing-text" style="font-weight: bold; margin-top: 10px;">Found in Drawing: 0/0 (0%)</div>
</div>
<!-- Drawing Content Section (Initially Hidden) -->
<div id="drawings-content" style="display: none;">
<h1 class="mb-4">Drawing Device Placement Progress</h1>
<p>Compares the Equipment Manifest against the extracted text from drawing files (.txt).</p>
<hr>
<div id="overall-drawing-progress" class="chart-container">
<span class="chart-label">Overall Drawing Progress</span>
<canvas id="overall-drawing-chart-canvas" class="panel-chart-canvas" style="max-width: 200px; max-height: 200px;"></canvas>
<div id="overall-drawing-text" style="font-weight: bold; margin-top: 10px;">Found in Drawing: 0/0 (0%)</div>
<h2>Drawing Progress by Control Panel</h2>
<div id="drawing-panels-progress">
<p>Loading panel data...</p>
</div>
</div>
<hr>
<!-- Conflicts Content Section (Initially Hidden) -->
<div id="conflicts-content" class="view-content" style="display: none;">
<h1 class="mb-4">SCADA/Drawing Conflicts (<span class="project-name-display"></span>) <span id="conflict-count" class="badge bg-warning ms-2">0</span></h1>
<p>Items found in SCADA views but <strong>not</strong> found in the extracted drawing text files.</p>
<h2>Drawing Progress by Control Panel</h2>
<div id="drawing-panels-progress">
<p>Loading panel data...</p>
<div id="panels-conflicts">
<p>Loading conflict data...</p>
</div>
</div>
</div>
</div> <!-- End project-content -->
<!-- Conflicts Content Section (Initially Hidden) -->
<div id="conflicts-content" style="display: none;">
<h1 class="mb-4">SCADA/Drawing Conflicts <span id="conflict-count" class="badge bg-warning ms-2">0</span></h1>
<p>Items found in SCADA views but <strong>not</strong> found in the extracted drawing text files.</p>
<div id="panels-conflicts">
<p>Loading conflict data...</p>
</div>
</div>
</div>
</div> <!-- End container -->
<!-- Status Bar -->
<div class="status-bar">
<span id="status-message">Initializing...</span> | Last Commit: <span id="last-commit">N/A</span>
Status (<span id="selected-project-status-name">...</span>): <span id="status-message">Initializing...</span> | Last Commit: <span id="last-commit">N/A</span>
</div>
<!-- Bootstrap Modal for Details -->
<!-- Bootstrap Modal for Details (remains the same structure, content filled by JS) -->
<div class="modal fade" id="detailsModal" tabindex="-1" aria-labelledby="detailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
@ -146,517 +131,53 @@
</div>
</div>
<!-- NEW: Add Project Modal -->
<div class="modal fade" id="addProjectModal" tabindex="-1" aria-labelledby="addProjectModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addProjectModalLabel">Add New Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addProjectForm" enctype="multipart/form-data">
<div class="mb-3">
<label for="projectName" class="form-label">Project Name</label>
<input type="text" class="form-control" id="projectName" name="projectName" required>
<div class="form-text">Use only letters, numbers, underscores, or hyphens.</div>
</div>
<div class="mb-3">
<label for="repoUrl" class="form-label">Git Repository URL</label>
<input type="url" class="form-control" id="repoUrl" name="repoUrl" required>
<div class="form-text">The URL will be used for cloning after restart.</div>
</div>
<div class="mb-3">
<label for="manifestFile" class="form-label">Manifest CSV File</label>
<input class="form-control" type="file" id="manifestFile" name="manifestFile" accept=".csv" required>
</div>
<div class="mb-3">
<label for="pdfFiles" class="form-label">Drawing PDF Files</label>
<input class="form-control" type="file" id="pdfFiles" name="pdfFiles" accept=".pdf" multiple required>
</div>
<div id="addProjectStatus" class="mt-3" style="display: none;"></div>
<button type="submit" class="btn btn-primary">Add Project & Prepare for Restart</button>
</form>
</div>
</div>
</div>
</div>
<!-- End Add Project Modal -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Pass initial data to JavaScript -->
<script>
// --- Global State Variables ---
let chartInstancesScada = {}; // Separate instances for SCADA
let chartInstancesDrawing = {}; // Separate instances for Drawing
let progressDetailsData = {}; // Stores the raw data from SSE (shared)
let previousCommitHash = null; // Single hash for the whole page
let detailsModalInstance = null;
let currentVisibleSection = 'scada'; // Track visible section: 'scada', 'drawing', 'conflicts'
// --- Chart Configurations ---
const scadaChartLabels = ['Found in SCADA', 'Not Found in SCADA'];
const scadaChartColors = ['rgb(13, 110, 253)', 'rgb(220, 53, 69)'];
const drawingChartLabels = ['Found in Drawing', 'Not Found in Drawing'];
const drawingChartColors = ['rgb(25, 135, 84)', 'rgb(220, 53, 69)'];
// Map backend list keys for modal clicks (can be combined or kept separate if needed)
const scadaListKeysMap = {
found: ['found_both_list', 'found_scada_only_list'],
notFound: ['found_drawing_only_list', 'missing_list']
// Embed initial data directly into the page for faster initial load
const initialServerData = {
projects: {{ projects | tojson }},
status: {{ initial_statuses | tojson }}
// Note: Full progress data is not embedded initially, fetched via SSE
};
const drawingListKeysMap = {
found: ['found_both_list', 'found_drawing_only_list'],
notFound: ['found_scada_only_list', 'missing_list']
};
// --- Debounce Utility (Only need one) ---
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// --- Chart Click Handler (Needs context: SCADA or Drawing?) ---
function handleChartClick(event, elements, chart, context) { // Added context
if (elements.length > 0) {
const clickedElementIndex = elements[0].index;
const isOverallChart = chart.canvas.id.startsWith('overall-'); // More robust check
const identifier = isOverallChart ? '__overall__' : chart.canvas.id.replace(`chart-${context}-`, ''); // Use context
const categoryType = clickedElementIndex === 0 ? 'found' : 'notFound';
showDetailsModal(identifier, categoryType, context); // Pass context to modal
}
}
// --- Core UI Update Functions (One for each section) ---
function updateUIScadaCore(data) {
console.log("Running core SCADA UI redraw logic for commit:", data.last_commit);
progressDetailsData = data.progress; // Update shared raw data
// --- Overall SCADA Chart ---
const overallData = progressDetailsData.overall;
const overallTotal = overallData.total_csv;
const overallFoundScada = overallData.found_both + overallData.found_scada_only;
const overallNotFoundScada = overallData.found_drawing_only + overallData.missing_both;
const overallPercentageFound = overallTotal > 0 ? ((overallFoundScada / overallTotal) * 100).toFixed(1) : 0;
const overallChartCounts = [overallFoundScada, overallNotFoundScada];
document.getElementById('overall-scada-text').textContent = `Found in SCADA: ${overallFoundScada}/${overallTotal} (${overallPercentageFound}%)`;
// --- Only update/create chart if section is visible ---
const isSectionVisible = (currentVisibleSection === 'scada');
if (isSectionVisible) {
const overallScadaCanvas = document.getElementById('overall-scada-chart-canvas');
if (chartInstancesScada['overall']) {
if (JSON.stringify(chartInstancesScada['overall'].data.datasets[0].data) !== JSON.stringify(overallChartCounts)) {
chartInstancesScada['overall'].data.datasets[0].data = overallChartCounts;
chartInstancesScada['overall'].update('none');
}
} else if (overallScadaCanvas) {
console.log("Creating overall SCADA chart (visible).");
const ctxOverall = overallScadaCanvas.getContext('2d');
chartInstancesScada['overall'] = new Chart(ctxOverall, createChartConfig(overallChartCounts, overallTotal, 'scada', 'overall'));
}
} else {
// If section is not visible, destroy the chart instance if it exists
if (chartInstancesScada['overall']) {
console.log("Destroying hidden overall SCADA chart.");
chartInstancesScada['overall'].destroy();
delete chartInstancesScada['overall'];
}
}
// --- SCADA Panel Charts ---
const panelsContainer = document.getElementById('scada-panels-progress');
const panelsData = progressDetailsData.panels || {};
updatePanelCharts(panelsContainer, panelsData, chartInstancesScada, 'scada');
console.log("Finished SCADA UI core redraw.");
}
function updateUIDrawingCore(data) {
console.log("Running core Drawing UI redraw logic for commit:", data.last_commit);
progressDetailsData = data.progress; // Update shared raw data
// --- Overall Drawing Chart ---
const overallData = progressDetailsData.overall;
const overallTotal = overallData.total_csv;
const overallFoundDrawing = overallData.found_both + overallData.found_drawing_only;
const overallNotFoundDrawing = overallData.found_scada_only + overallData.missing_both;
const overallPercentageFound = overallTotal > 0 ? ((overallFoundDrawing / overallTotal) * 100).toFixed(1) : 0;
const overallChartCounts = [overallFoundDrawing, overallNotFoundDrawing];
document.getElementById('overall-drawing-text').textContent = `Found in Drawing: ${overallFoundDrawing}/${overallTotal} (${overallPercentageFound}%)`;
// --- Only update/create chart if section is visible ---
const isSectionVisible = (currentVisibleSection === 'drawings');
if (isSectionVisible) {
const overallDrawingCanvas = document.getElementById('overall-drawing-chart-canvas');
if (chartInstancesDrawing['overall']) {
if (JSON.stringify(chartInstancesDrawing['overall'].data.datasets[0].data) !== JSON.stringify(overallChartCounts)) {
chartInstancesDrawing['overall'].data.datasets[0].data = overallChartCounts;
chartInstancesDrawing['overall'].update('none');
}
} else if (overallDrawingCanvas) {
console.log("Creating overall drawing chart (visible).");
const ctxOverall = overallDrawingCanvas.getContext('2d');
chartInstancesDrawing['overall'] = new Chart(ctxOverall, createChartConfig(overallChartCounts, overallTotal, 'drawing', 'overall'));
}
} else {
// If section is not visible, destroy the chart instance if it exists
if (chartInstancesDrawing['overall']) {
console.log("Destroying hidden overall Drawing chart.");
chartInstancesDrawing['overall'].destroy();
delete chartInstancesDrawing['overall'];
}
}
// --- Drawing Panel Charts (call updatePanelCharts, which also checks visibility/destroys) ---
const panelsContainer = document.getElementById('drawing-panels-progress');
const panelsData = progressDetailsData.panels || {};
console.log(`[updateUIDrawingCore] Found drawing panels container:`, panelsContainer ? panelsContainer.id : 'Not Found'); // Added Log
updatePanelCharts(panelsContainer, panelsData, chartInstancesDrawing, 'drawings'); // Changed context to plural 'drawings'
console.log("Finished Drawing UI core redraw.");
}
function updateUIConflictsCore(data) {
console.log("Running core Conflicts UI redraw logic for commit:", data.last_commit);
progressDetailsData = data.progress; // Update shared raw data
const panelsContainer = document.getElementById('panels-conflicts');
panelsContainer.innerHTML = ''; // Clear previous
const panelsData = progressDetailsData.panels;
let totalConflicts = 0;
let panelsWithConflicts = 0;
if (!panelsData || Object.keys(panelsData).length === 0) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No panel data available yet.</p>';
} else {
const sortedPanels = Object.keys(panelsData).sort();
sortedPanels.forEach(panelName => {
const panel = panelsData[panelName];
const conflictsList = panel.found_scada_only_list || [];
if (conflictsList.length > 0) {
panelsWithConflicts++;
totalConflicts += conflictsList.length;
// ... (Create header and table as in conflicts.html) ...
const panelHeader = document.createElement('h4');
panelHeader.className = 'mt-4 mb-2';
panelHeader.textContent = `${panelName} (${conflictsList.length} conflicts)`;
panelsContainer.appendChild(panelHeader);
const table = document.createElement('table');
table.className = 'table table-sm table-striped table-hover table-bordered';
const thead = table.createTHead();
thead.innerHTML = `<tr><th>Alias</th><th>Panel</th><th>SCADA Status</th><th>Drawing Status</th><th>Equipment Type</th><th>Type of Conveyor</th></tr>`;
const tbody = table.createTBody();
conflictsList.sort((a, b) => a.alias.localeCompare(b.alias)).forEach(item => {
const row = tbody.insertRow();
row.classList.add('table-warning');
row.insertCell().textContent = item.alias;
row.insertCell().textContent = item.control_panel;
row.insertCell().innerHTML = '<span class="status-yes">Yes</span>';
row.insertCell().innerHTML = '<span class="status-no">No</span>';
row.insertCell().textContent = item.equipment_type || 'N/A';
row.insertCell().textContent = item.conveyor_type || 'N/A';
});
panelsContainer.appendChild(table);
}
});
if (panelsWithConflicts === 0) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No conflicts found across all panels.</p>';
}
}
// Update total count badge
const countBadge = document.getElementById('conflict-count');
if (countBadge) {
countBadge.textContent = totalConflicts;
countBadge.style.display = totalConflicts > 0 ? 'inline-block' : 'none';
}
console.log("Finished Conflicts UI core redraw.");
}
// --- Generic Panel Chart Update Logic ---
function updatePanelCharts(panelsContainer, panelsData, chartInstances, context) { // context: 'scada' or 'drawing'
const incomingPanelNames = new Set(Object.keys(panelsData).sort());
const existingInstanceNames = new Set(Object.keys(chartInstances).filter(k => k !== 'overall'));
// --- Check if the context matches the currently visible section ---
const isSectionVisible = (context === currentVisibleSection);
if (!isSectionVisible) {
// If section is not visible, destroy existing panel chart instances for this context
console.log(`Destroying hidden panel charts for context: ${context}`);
existingInstanceNames.forEach(panelName => {
if (chartInstances[panelName]) {
chartInstances[panelName].destroy();
delete chartInstances[panelName];
}
});
// Don't proceed further if the section is hidden
return;
}
if (incomingPanelNames.size > 0) {
const loadingMsg = panelsContainer.querySelector('p');
if (loadingMsg) { loadingMsg.remove(); }
incomingPanelNames.forEach(panelName => {
const panel = panelsData[panelName];
const panelTotal = panel.total;
let panelChartCounts;
if (context === 'scada') {
panelChartCounts = [panel.found_both + panel.found_scada_only, panel.found_drawing_only + panel.missing_both];
} else { // drawing
panelChartCounts = [panel.found_both + panel.found_drawing_only, panel.found_scada_only + panel.missing_both];
}
// --- Only update/create chart if section is visible ---
if (isSectionVisible) {
if (chartInstances[panelName]) {
if (JSON.stringify(chartInstances[panelName].data.datasets[0].data) !== JSON.stringify(panelChartCounts)) {
chartInstances[panelName].data.datasets[0].data = panelChartCounts;
chartInstances[panelName].update('none');
}
} else {
let canvas = document.getElementById(`chart-${context}-${panelName}`); // Use context in ID
if (canvas) {
console.log(`Recreating ${context} chart instance for panel (visible): ${panelName}`);
const ctx = canvas.getContext('2d');
chartInstances[panelName] = new Chart(ctx, createChartConfig(panelChartCounts, panelTotal, context, panelName));
} else {
console.log(`Creating new ${context} panel elements and chart (visible) for: ${panelName}`);
const chartContainer = document.createElement('div');
chartContainer.id = `chart-container-${context}-${panelName}`; // Use context in ID
chartContainer.className = 'chart-container';
const label = document.createElement('span');
label.className = 'chart-label'; label.textContent = panelName;
canvas = document.createElement('canvas'); // Reassign canvas variable
canvas.id = `chart-${context}-${panelName}`; // Use context in ID
canvas.className = 'panel-chart-canvas';
chartContainer.appendChild(label);
chartContainer.appendChild(canvas);
// Added Log before append
console.log(`[updatePanelCharts] Appending chartContainer (${chartContainer.id}) to panelsContainer (${panelsContainer ? panelsContainer.id : 'null'})`);
panelsContainer.appendChild(chartContainer); // Append to the main panels progress div
const ctx = canvas.getContext('2d');
chartInstances[panelName] = new Chart(ctx, createChartConfig(panelChartCounts, panelTotal, context, panelName));
}
}
}
// --- End visibility check ---
});
} else {
if (!panelsContainer.querySelector('p')) {
panelsContainer.innerHTML = '<p class="text-center fst-italic">No panel data available yet.</p>';
}
}
existingInstanceNames.forEach(panelName => {
if (!incomingPanelNames.has(panelName)) {
console.log(`Removing ${context} panel elements and chart for: ${panelName}`);
// Ensure chart is destroyed before removing element
if (chartInstances[panelName]) {
chartInstances[panelName].destroy();
delete chartInstances[panelName];
}
const chartElement = document.getElementById(`chart-container-${context}-${panelName}`); // Use context
if (chartElement) {
chartElement.remove();
}
}
});
}
// --- Generic Helper to create chart config --- Needs context ---
function createChartConfig(chartCounts, total, context, identifier) { // identifier is 'overall' or panelName
const labels = context === 'scada' ? scadaChartLabels : drawingChartLabels;
const colors = context === 'scada' ? scadaChartColors : drawingChartColors;
const datasetLabel = context === 'scada' ? 'SCADA Match' : 'Drawing Match';
return {
type: 'pie',
data: {
labels: labels,
datasets: [{
label: datasetLabel,
data: chartCounts,
backgroundColor: colors,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
onClick: (event, elements, chart) => handleChartClick(event, elements, chart, context), // Pass context
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(ctxTooltip) {
let label = ctxTooltip.label || '';
if (label) label += ': ';
const value = ctxTooltip.parsed;
if (value !== null) label += value;
// Use overallTotal for overall chart, panelTotal otherwise (How to get panelTotal here? Needs rethinking)
// Workaround: Don't show percentage on panel tooltips for now
const chartTotal = (identifier === 'overall' && progressDetailsData.overall) ? progressDetailsData.overall.total_csv : null;
if (chartTotal && chartTotal > 0) {
label += ` (${((value / chartTotal) * 100).toFixed(1)}%)`;
}
return label;
}
}
}
}
}
};
}
// --- Wrapper function called by debouncer (Handles all sections) ---
function processUpdate(data) {
console.log("Processing update for commit:", data.last_commit);
// Always update status bar and commit hash text immediately
document.getElementById('status-message').textContent = data.status;
document.getElementById('last-commit').textContent = data.last_commit || 'N/A';
// *** Strict Check: Only proceed if commit hash has changed ***
if (data.last_commit && data.last_commit !== previousCommitHash) {
console.log("Commit hash changed (" + (previousCommitHash || 'None') + " -> " + data.last_commit + ") or initial load. Queueing core redraw.");
previousCommitHash = data.last_commit;
// Defer the core UI update calls
setTimeout(() => {
// Update all sections - they have internal checks/efficiency
updateUIScadaCore(data);
updateUIDrawingCore(data);
updateUIConflictsCore(data);
}, 0);
} else {
console.log("Commit hash unchanged (" + previousCommitHash + "), skipping core UI redraw.");
}
}
// --- Debounced version of the processing function ---
const debouncedProcessUpdate = debounce(processUpdate, 250); // Single debouncer
// --- Modal Display Function (Needs context) ---
function showDetailsModal(identifier, categoryType, context) { // Added context
let sourceData = null;
let panelNameDisplay = "";
const listKeysMap = context === 'scada' ? scadaListKeysMap : drawingListKeysMap;
const listTypeLabel = categoryType === 'found'
? (context === 'scada' ? 'Found in SCADA' : 'Found in Drawing')
: (context === 'scada' ? 'Not Found in SCADA' : 'Not Found in Drawing');
if (identifier === '__overall__') {
sourceData = progressDetailsData.overall;
panelNameDisplay = "Overall";
} else {
sourceData = progressDetailsData.panels ? progressDetailsData.panels[identifier] : null;
panelNameDisplay = identifier;
}
if (!sourceData) { /* ... error handling ... */ return; }
const backendListKeys = listKeysMap[categoryType];
if (!backendListKeys) { /* ... error handling ... */ return; }
let combinedDataList = [];
backendListKeys.forEach(key => {
if (sourceData[key]) {
combinedDataList = combinedDataList.concat(sourceData[key]);
}
});
if (combinedDataList.length === 0) { /* ... alert handling ... */ return; }
const modalTitleElement = document.getElementById('detailsModalLabel');
const modalTableBody = document.querySelector('#detailsModal .modal-body tbody');
modalTitleElement.innerHTML = `${listTypeLabel} Items for ${panelNameDisplay} <span class="badge bg-secondary ms-2">${combinedDataList.length}</span>`;
modalTableBody.innerHTML = '';
combinedDataList.sort((a, b) => a.alias.localeCompare(b.alias)).forEach(item => {
const row = document.createElement('tr');
row.insertCell().textContent = item.alias;
row.insertCell().textContent = item.control_panel;
const scadaCell = row.insertCell(); scadaCell.innerHTML = item.found_scada ? '<span class="status-yes">Yes</span>' : '<span class="status-no">No</span>';
const drawingCell = row.insertCell(); drawingCell.innerHTML = item.found_drawing ? '<span class="status-yes">Yes</span>' : '<span class="status-no">No</span>';
row.insertCell().textContent = item.equipment_type || 'N/A';
row.insertCell().textContent = item.conveyor_type || 'N/A';
if (item.found_scada && !item.found_drawing) { row.classList.add('table-warning'); }
modalTableBody.appendChild(row);
});
if (!detailsModalInstance) {
detailsModalInstance = new bootstrap.Modal(document.getElementById('detailsModal'));
}
detailsModalInstance.show();
}
// --- Navigation Handling ---
function showSection(sectionId) {
console.log("Showing section:", sectionId);
document.getElementById('scada-content').style.display = 'none';
document.getElementById('drawings-content').style.display = 'none';
document.getElementById('conflicts-content').style.display = 'none';
const elementToShow = document.getElementById(`${sectionId}-content`);
if (elementToShow) {
elementToShow.style.display = 'block';
currentVisibleSection = sectionId;
// --- Trigger update for the now-visible section ---
// The update function will check visibility internally before drawing charts.
if (progressDetailsData && Object.keys(progressDetailsData).length > 0) {
const updateData = { progress: progressDetailsData }; // Pass existing data
console.log(`Calling update function for now-visible section: ${sectionId}`);
// Use setTimeout to ensure DOM update (display: block) is processed first
if (sectionId === 'scada') {
updateUIScadaCore(updateData);
} else if (sectionId === 'drawings') {
updateUIDrawingCore(updateData);
} else if (sectionId === 'conflicts') {
updateUIConflictsCore(updateData);
}
} else {
console.log(`Section ${sectionId} shown, but no progress data yet.`);
// If data arrives later, the debouncedProcessUpdate will handle drawing
// for the currently visible section.
}
// --- End section update trigger ---
} else {
console.error("Attempted to show unknown section:", sectionId);
document.getElementById('scada-content').style.display = 'block'; // Default back to SCADA
currentVisibleSection = 'scada';
}
// Update active nav link
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
// Use href attribute to match sectionId
const targetSection = link.getAttribute('data-target-section');
if (targetSection === sectionId) {
link.classList.add('active');
}
});
}
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM Loaded, setting up navigation...");
document.querySelectorAll('.nav-link').forEach(link => {
// Store target section ID in a data attribute from href
const href = link.getAttribute('href');
let targetSection = 'scada'; // Default
if (href === '/drawings') targetSection = 'drawings'; // Use plural to match ID
else if (href === '/conflicts') targetSection = 'conflicts'; // Use plural to match ID
link.setAttribute('data-target-section', targetSection);
link.addEventListener('click', (event) => {
event.preventDefault(); // Prevent page reload
const sectionId = link.getAttribute('data-target-section');
showSection(sectionId);
});
});
// Show initial section (SCADA by default)
showSection('scada');
});
// --- Connect to SSE stream (Single connection) ---
console.log("Initializing SSE connection...");
const eventSource = new EventSource("/stream");
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
debouncedProcessUpdate(data); // Call the single debounced processor
} catch (error) {
console.error("Error parsing SSE data:", error);
document.getElementById('status-message').textContent = 'Error processing update from server.';
}
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
document.getElementById('status-message').textContent = 'Connection to server lost. Retrying...';
};
console.log("SSE handler set up.");
</script>
<script src="/static/js/script.js"></script>
</body>
</html>

174
utils.py Normal file
View File

@ -0,0 +1,174 @@
import os
import re
import glob # Import glob for finding CSV files
import config
# Need pypdf for text extraction
from pypdf import PdfReader
def discover_projects():
"""Discovers projects by listing subdirectories in the PROJECTS_ROOT_DIR."""
projects = []
if not os.path.exists(config.PROJECTS_ROOT_DIR):
print(f"Warning: Projects root directory not found: {config.PROJECTS_ROOT_DIR}")
return []
for item in os.listdir(config.PROJECTS_ROOT_DIR):
item_path = os.path.join(config.PROJECTS_ROOT_DIR, item)
if os.path.isdir(item_path):
# Simple check: assume any directory is a project
# More robust check could look for specific files/folders inside
projects.append(item)
print(f"Discovered projects: {projects}")
return projects
def get_project_base_path(project_name):
"""Returns the absolute path to a specific project's directory."""
return os.path.join(config.PROJECTS_ROOT_DIR, project_name)
def get_repo_path(project_name):
"""Returns the absolute path to the repository directory for a given project."""
# Assume repo is always in a subdir named 'repo' within the project base
return os.path.join(get_project_base_path(project_name), "repo")
def find_csv_path(project_name):
"""Finds the first CSV file within the project's base directory."""
project_base = get_project_base_path(project_name)
csv_files = glob.glob(os.path.join(project_base, '*.csv'))
if csv_files:
if len(csv_files) > 1:
print(f"Warning: Multiple CSV files found in {project_base}. Using the first one: {csv_files[0]}")
return csv_files[0]
else:
print(f"Error: No CSV file found in project directory: {project_base}")
return None
def get_views_dir_path(project_name):
"""Returns the absolute path to the SCADA views directory within the project's repo."""
repo_path = get_repo_path(project_name)
# Dynamically find the SCADA data directory (e.g., 'MTN6_SCADA')
scada_data_dir = None
try:
for item in os.listdir(repo_path):
item_path = os.path.join(repo_path, item)
# Simple check: find first directory ending with '_SCADA' (case-insensitive)
if os.path.isdir(item_path) and item.upper().endswith('_SCADA'):
scada_data_dir = item_path
print(f"[{project_name}] Found SCADA data directory: {scada_data_dir}")
break # Use the first one found
except FileNotFoundError:
print(f"Warning: Repo path not found for project '{project_name}' at '{repo_path}' when searching for SCADA dir.")
# Fall through to return a potentially invalid path
except Exception as e:
print(f"Warning: Error searching for SCADA dir in '{repo_path}': {e}")
# Fall through
if not scada_data_dir:
print(f"Warning: Could not automatically find a *_SCADA directory in {repo_path}. Using fallback path structure.")
# Fallback: Reconstruct a path assuming a fixed name (less ideal)
# Or simply return None or let it fail? Returning the best guess path:
scada_data_dir = os.path.join(repo_path, f"{project_name}_SCADA") # Guess the folder name
# Append the common relative path from config
return os.path.join(scada_data_dir, config.VIEWS_DIR_RELATIVE)
def get_text_output_dir_path(project_name):
"""Returns the absolute path to the extracted drawing text output directory for a project."""
# Uses the relative folder name from config
return os.path.join(get_project_base_path(project_name), config.TEXT_OUTPUT_FOLDER_RELATIVE)
def get_pdf_dir_path(project_name):
"""Returns the absolute path to the source PDF directory for a project."""
# ASSUMPTION: PDFs are stored in a 'pdfs' subdirectory within the project base path
# Adjust 'pdfs' if the actual directory name is different.
return os.path.join(get_project_base_path(project_name), 'pdfs')
def normalize(text):
"""Normalize string for comparison: lowercase, treat '-' and '_' the same, remove all whitespace."""
if not isinstance(text, str):
return ""
text = text.lower() # Convert to lowercase
text = text.replace('-', '_') # Replace hyphens with underscores
text = re.sub(r'\s+', '', text) # Remove ALL whitespace characters (including newlines)
return text
def extract_text_from_pdf(pdf_path, txt_path):
"""
Extracts text from a single PDF file and saves it to a TXT file.
Returns True on success (incl. writing empty file), False on failure.
"""
base_filename = os.path.basename(pdf_path)
print(f" [Extractor] Attempting to process: {base_filename}")
extracted_text = ""
success = False # Track overall success
reader = None # Initialize reader to None
try:
# --- Step 1: Open and Decrypt (if necessary) ---
try:
print(f" [Extractor] Opening PDF: {base_filename}")
reader = PdfReader(pdf_path)
print(f" [Extractor] PDF opened successfully: {base_filename}")
except Exception as open_err:
print(f" [Extractor] CRITICAL ERROR opening PDF {base_filename}: {open_err}")
# Log traceback for detailed debugging
import traceback
traceback.print_exc()
return False # Cannot proceed
if reader.is_encrypted:
print(f" [Extractor] PDF is encrypted: {base_filename}. Attempting decryption...")
try:
# Try decrypting with empty password - adjust if needed
reader.decrypt('')
print(f" [Extractor] Decryption successful (or not needed) for {base_filename}")
except Exception as decrypt_err:
print(f" [Extractor] WARNING: Could not decrypt PDF {base_filename}: {decrypt_err}. Skipping.")
return False # Treat decryption failure as critical for this file
# --- Step 2: Extract Text Page by Page ---
print(f" [Extractor] Starting page-by-page text extraction for: {base_filename} ({len(reader.pages)} pages)")
page_texts = []
for i, page in enumerate(reader.pages):
try:
# print(f" [Extractor] Extracting text from page {i+1}") # Can be verbose
page_text = page.extract_text()
if page_text:
page_texts.append(page_text)
# else: print(f" [Extractor] No text found on page {i+1}")
except Exception as page_err:
# Log page-specific errors but continue if possible
print(f" [Extractor] WARNING: Error extracting text from page {i+1} in {base_filename}: {page_err}")
# Decide if this is fatal for the file? For now, we continue.
extracted_text = "\n".join(page_texts)
print(f" [Extractor] Finished text extraction for {base_filename}. Total chars extracted: {len(extracted_text)}")
# Handle case where no text is extracted - write empty file to prevent re-attempts
if not extracted_text:
print(f" [Extractor] WARNING: No text extracted from {base_filename}. An empty TXT file will be created.")
# --- Step 3: Write to TXT File ---
print(f" [Extractor] Attempting to write TXT file: {os.path.basename(txt_path)}")
try:
with open(txt_path, 'w', encoding='utf-8') as txt_file:
txt_file.write(extracted_text)
print(f" [Extractor] Successfully wrote TXT file: {os.path.basename(txt_path)}")
success = True # Mark as successful
except Exception as write_err:
print(f" [Extractor] ERROR writing text file {os.path.basename(txt_path)}: {write_err}")
success = False # Failed to write
except FileNotFoundError:
# This should technically be caught by the initial open_err block now
print(f" [Extractor] ERROR: PDF file not found at {pdf_path}.")
success = False
except Exception as e:
# Catch-all for unexpected errors during the process
print(f" [Extractor] UNEXPECTED CRITICAL ERROR processing PDF {base_filename}: {e}")
import traceback
traceback.print_exc()
success = False
# --- No finally block needed as we return directly ---
print(f" [Extractor] Finished processing {base_filename}. Result: {'Success' if success else 'Failure'}")
return success