Skip to content

Commit

Permalink
Canvas Rewrite for Auton Paths
Browse files Browse the repository at this point in the history
## Summary
Rewriting the canvas system for drawing Auton Path 

### Todo
- [x] Pan and zoom
- [x] Readonly
- [x] Toolbox
  - [x] Shapes
  - [x] Pen Drawing
  - [ ] ~~Eraser~~
  - [x] Select
  - [x] Fill Shapes
  - [x] Undo/Redo
  - [x] Stroke Thickness
  - [x] History


### Issue in testing:
- [x] Shapes don't draw on mobile
- [x] Select objects to sync thickness of selected also updates robot defense slider
- [x] Go to origin doesn't work on mobile
- [x] Toolbar isn't visible in mobile
- [x] Keybinds doing the action twice Edit: Solution: `e.preventDefault();`
- [x] Show status message doesn't work

## Checklist

<!-- Put an x inside [ ] to check it, like so: [x] -->

- [x] If code changes were made then they have been tested.
    - [x] I have updated the documentation to reflect the changes.
- [x] This PR fixes an issue.
- [x] This PR adds something new (e.g. subsystem).
- [ ] This PR is **not** a code change (e.g. README, typehinting, examples, refactoring, ...)
  • Loading branch information
cherriae authored Feb 28, 2025
2 parents fb66d00 + bb3f9c9 commit e5f2e09
Show file tree
Hide file tree
Showing 14 changed files with 3,856 additions and 1,081 deletions.
82 changes: 82 additions & 0 deletions .sourcery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# 🪄 This is your project's Sourcery configuration file.

# You can use it to get Sourcery working in the way you want, such as
# ignoring specific refactorings, skipping directories in your project,
# or writing custom rules.

# 📚 For a complete reference to this file, see the documentation at
# https://docs.sourcery.ai/Configuration/Project-Settings/

# This file was auto-generated by Sourcery on 2025-02-27 at 18:52.

version: '1' # The schema version of this config file

ignore: # A list of paths or files which Sourcery will ignore.
- .git
- env
- .env
- .tox
- node_modules
- vendor
- venv
- .venv
- ~/.pyenv
- ~/.rye
- ~/.vscode
- .vscode
- ~/.cache
- ~/.config
- ~/.local

rule_settings:
enable:
- default
disable: [] # A list of rule IDs Sourcery will never suggest.
rule_types:
- refactoring
- suggestion
- comment
python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.

# rules: # A list of custom rules Sourcery will include in its analysis.
# - id: no-print-statements
# description: Do not use print statements in the test directory.
# pattern: print(...)
# language: python
# replacement:
# condition:
# explanation:
# paths:
# include:
# - test
# exclude:
# - conftest.py
# tests: []
# tags: []

# rule_tags: {} # Additional rule tags.

# metrics:
# quality_threshold: 25.0

# github:
# labels: []
# ignore_labels:
# - sourcery-ignore
# request_review: author
# sourcery_branch: sourcery/{base_branch}

# clone_detection:
# min_lines: 3
# min_duplicates: 2
# identical_clones_only: false

# proxy:
# url:
# ssl_certs_file:
# no_ssl_verify: false

# coding_assistant:
# project_description: ''
# enabled: true
# recipe_prompts: {}
140 changes: 118 additions & 22 deletions app/scout/scouting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,34 @@ def __init__(self, mongo_uri):

def _ensure_collections(self):
"""Ensure required collections exist"""
if "team_data" not in self.db.list_collection_names():
collections = self.db.list_collection_names()
if "team_data" not in collections:
self._create_team_data_collection()
if "pit_scouting" not in collections:
self.db.create_collection("pit_scouting")
self.db.pit_scouting.create_index([("team_number", 1)])
self.db.pit_scouting.create_index([("scouter_id", 1)])
logger.info("Created pit_scouting collection and indexes")

# Fix any string scouter_ids in pit_scouting collection
self._migrate_pit_scouting_scouter_ids()

def _migrate_pit_scouting_scouter_ids(self):
"""Migrate string scouter_ids to ObjectId in pit_scouting collection"""
try:
# Find documents where scouter_id is a string
for doc in self.db.pit_scouting.find({"scouter_id": {"$type": "string"}}):
try:
# Convert string to ObjectId
self.db.pit_scouting.update_one(
{"_id": doc["_id"]},
{"$set": {"scouter_id": ObjectId(doc["scouter_id"])}}
)
logger.info(f"Migrated pit scouting document {doc['_id']} scouter_id to ObjectId")
except Exception as e:
logger.error(f"Failed to migrate pit scouting document {doc['_id']}: {str(e)}")
except Exception as e:
logger.error(f"Error during pit scouting migration: {str(e)}")

def connect(self):
"""Establish connection to MongoDB with basic error handling"""
Expand All @@ -32,9 +58,15 @@ def connect(self):
self.db = self.client.get_default_database()
logger.info("Successfully connected to MongoDB")

# Ensure team_data collection exists
if "team_data" not in self.db.list_collection_names():
# Ensure collections exist
collections = self.db.list_collection_names()
if "team_data" not in collections:
self._create_team_data_collection()
if "pit_scouting" not in collections:
self.db.create_collection("pit_scouting")
self.db.pit_scouting.create_index([("team_number", 1)])
self.db.pit_scouting.create_index([("scouter_id", 1)])
logger.info("Created pit_scouting collection and indexes")
except Exception as e:
logger.error(f"Failed to connect to MongoDB: {str(e)}")
raise
Expand Down Expand Up @@ -531,7 +563,7 @@ def add_pit_scouting(self, data):
self.ensure_connected()
try:
team_number = int(data["team_number"])
scouter_id = data["scouter_id"]
scouter_id = ObjectId(data["scouter_id"]) # Convert to ObjectId

# Check if this team is already scouted by someone from the same team
pipeline = [
Expand All @@ -552,13 +584,16 @@ def add_pit_scouting(self, data):
]

existing_entries = list(self.db.pit_scouting.aggregate(pipeline))
current_user = self.db.users.find_one({"_id": ObjectId(scouter_id)})
current_user = self.db.users.find_one({"_id": scouter_id})

for entry in existing_entries:
if entry.get("scouter", {}).get("teamNumber") == current_user.get("teamNumber"):
logger.warning(f"Team {team_number} has already been pit scouted by team {current_user.get('teamNumber')}")
return False

# Ensure scouter_id is ObjectId in the data
data["scouter_id"] = scouter_id

result = self.db.pit_scouting.insert_one(data)
return bool(result.inserted_id)

Expand Down Expand Up @@ -621,39 +656,100 @@ def get_pit_scouting(self, team_number):
@with_mongodb_retry(retries=3, delay=2)
def get_all_pit_scouting(self, user_team_number=None, user_id=None):
"""Get all pit scouting data with team-based access control"""
self.ensure_connected()
try:
pipeline = [
{
"$lookup": {
"from": "users",
"localField": "scouter_id",
"foreignField": "_id",
"as": "scouter"
}
},
{"$unwind": "$scouter"},
]
logger.info(f"Fetching pit scouting data for user_id: {user_id}, team_number: {user_team_number}")

# First check if we have any data at all in the collection
total_count = self.db.pit_scouting.count_documents({})
logger.info(f"Total documents in pit_scouting collection: {total_count}")

# Log the raw documents for debugging
raw_docs = list(self.db.pit_scouting.find())
for doc in raw_docs:
logger.info(f"Raw pit scouting document: {doc}")
if 'scouter_id' in doc:
scouter = self.db.users.find_one({"_id": doc['scouter_id']})
logger.info(f"Associated scouter: {scouter}")

# Log the user's info
user_info = self.db.users.find_one({"_id": ObjectId(user_id)})
logger.info(f"User info: {user_info}")

# Add match stage for filtering based on team number or user ID
if user_team_number:
pipeline.append({
# If user has a team number, show data from their team and their own data
match_stage = {
"$match": {
"$or": [
{"scouter.teamNumber": user_team_number},
{"scouter._id": ObjectId(user_id)}
]
}
})
}
logger.info(f"Using team filter with team number: {user_team_number}")
else:
pipeline.append({
# If user has no team, only show their own data
match_stage = {
"$match": {
"scouter._id": ObjectId(user_id)
}
})
}
logger.info("Using individual user filter")

pipeline = [
{
"$lookup": {
"from": "users",
"localField": "scouter_id",
"foreignField": "_id",
"as": "scouter",
}
},
{"$unwind": "$scouter"},
*(
match_stage,
{
"$project": {
"_id": 1,
"team_number": 1,
"drive_type": 1,
"swerve_modules": 1,
"motor_details": 1,
"motor_count": 1,
"dimensions": 1,
"mechanisms": 1,
"programming_language": 1,
"autonomous_capabilities": 1,
"driver_experience": 1,
"notes": 1,
"created_at": 1,
"updated_at": 1,
"scouter_id": "$scouter._id",
"scouter_name": "$scouter.username",
"scouter_team": "$scouter.teamNumber",
}
},
),
]
# Log the full pipeline for debugging
logger.info(f"MongoDB pipeline: {pipeline}")

# Execute the pipeline on the pit_scouting collection
pit_data = list(self.db.pit_scouting.aggregate(pipeline))
logger.info(f"Retrieved {len(pit_data)} pit scouting records")

# Log the first record if any exist (excluding sensitive info)
if pit_data:
sample_record = pit_data[0].copy()
if "scouter_id" in sample_record:
del sample_record["scouter_id"]
logger.info(f"Sample record: {sample_record}")

return pit_data

return list(self.db.pit_scouting.aggregate(pipeline))
except Exception as e:
logger.error(f"Error fetching pit scouting data: {str(e)}")
logger.error(f"Error fetching pit scouting data: {str(e)}", exc_info=True)
return []

@with_mongodb_retry(retries=3, delay=2)
Expand Down
4 changes: 4 additions & 0 deletions app/static/css/global.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Global Font Settings */
*, body {
font-family: 'SecondaryFont', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
Expand Down
Loading

0 comments on commit e5f2e09

Please sign in to comment.