Files
game-cards-poker-design/backend/apps/projects/models.py
Developer 5ca000b8ab feat: 预设素材库 - 4 套主题 × 4 张图 = 16 张 PNG
主题:classical 古典宫廷(王子/皇后/国王/小丑)
      modern    现代人物(小孩/女青年/男青年/小丑鱼)
      astronomy 天文(星星/月亮/太阳/黑洞)
      minimal   简笔符号(圆点/♀/♂/叉)

改动:
- 新增 LibraryAsset 模型(全局素材库,theme_id/role/asset_id 索引)
- 新增 seed_library 管理命令,用 Pillow 画 16 张 PNG 素材
- 新增 /api/projects/library/ 列表 API
- 新增 /api/projects/{pid}/library/{id}/apply/ 应用 API
  把预设素材复制到 projects/<pid>/joker 或 face_card 下,作为该牌位的素材
- AssetPanel 加 tab 切换:「我的素材」+「预设主题」,主题可筛选、点击套用到当前牌
- 修复 generate_card_png 的 joker asset 匹配 bug:which 应该是 card_key(含前缀)才能匹配 asset_key

设计要点:
- 预设素材只画上半身(y=0~150),下半留空,让系统的'自动对称'流水线正确工作
- 预设素材 PNG 200×300,深色描边 + 半透明浅色填充,在任意牌面背景上叠加都清晰
2026-06-02 14:39:52 +08:00

132 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
import uuid
def default_design():
"""默认设计配置(整副牌共享)"""
return {
# 全局背景色(整副牌默认用这个,个别牌可覆盖)
'background_color': '#FFFFFF',
'background_image': None, # 整副牌背景图,相对 media 的路径
# 整副牌边框
'border_color': '#333333',
'border_width': 2,
# 4 个花色符号:可以上传图片,也可保持 None用字体符号
'suit_symbols': {
'spade': {'type': 'text', 'value': '', 'asset_id': None, 'color': '#000000'},
'heart': {'type': 'text', 'value': '', 'asset_id': None, 'color': '#E53935'},
'club': {'type': 'text', 'value': '', 'asset_id': None, 'color': '#000000'},
'diamond': {'type': 'text', 'value': '', 'asset_id': None, 'color': '#E53935'},
},
# 数字牌角标和中心花色符号的大小(占牌面宽度比例)
'corner_size_ratio': 0.13,
'pip_size_ratio': 0.16,
# 字体
'font_family': 'Times New Roman',
'font_color': '#000000', # 角标数字颜色
# 角标布局微调(相对位置 0~1
'corner_offset': {'x': 0, 'y': 0},
}
def default_card_overrides():
"""每张牌可独立覆盖的项目级设置key=card_key, value 覆盖项)"""
return {
# 例如 'joker-big': { 'background_color': '#1B5E20' }
}
class Project(models.Model):
"""项目配置模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100)
template_id = models.CharField(max_length=50, default='classic')
card_width = models.IntegerField(default=750)
card_height = models.IntegerField(default=1050)
# 项目级设计配置
design = models.JSONField(default=default_design)
# 每张牌对项目级配置的覆盖
card_overrides = models.JSONField(default=default_card_overrides)
# 数字牌花色位置微调(相对 0~1
# { '1': [{'dx':0,'dy':0,'scale':1}, ...], '2': [...], ... }
number_layout = models.JSONField(default=dict)
# JQK 人物图的水平翻转(每张牌独立)
face_orientations = models.JSONField(default=dict)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# 导出设置
export_resolution = models.CharField(max_length=20, default='standard')
export_include_back = models.BooleanField(default=True)
def __str__(self):
return self.name
class Meta:
ordering = ['-updated_at']
class Asset(models.Model):
"""项目素材模型"""
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='assets')
asset_type = models.CharField(max_length=20) # 'suit_symbol', 'face_card', 'joker', 'back', 'border', 'background'
asset_key = models.CharField(max_length=50) # 如 'spade', 'heart-J', 'big_joker'
file_path = models.CharField(max_length=255, blank=True) # 相对于media目录
file_name = models.CharField(max_length=100, blank=True)
width = models.IntegerField(null=True, blank=True)
height = models.IntegerField(null=True, blank=True)
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.asset_type}:{self.asset_key}"
class Meta:
ordering = ['-uploaded_at']
class CardLayer(models.Model):
"""牌面图层配置模型(图层顺序、可见性等)"""
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='layers')
card_type = models.CharField(max_length=20) # 'number', 'face', 'joker', 'back'
card_key = models.CharField(max_length=30) # 'spade-A', 'heart-K', 'joker-big'
layer_name = models.CharField(max_length=50)
layer_type = models.CharField(max_length=20) # 'background', 'border', 'pattern', 'image', 'text', 'symbol'
visible = models.BooleanField(default=True)
locked = models.BooleanField(default=False)
opacity = models.FloatField(default=1.0)
z_index = models.IntegerField(default=0)
# 图层属性JSON存储
properties = models.JSONField(default=dict)
def __str__(self):
return f"{self.card_key}-{self.layer_name}"
class Meta:
ordering = ['card_key', 'z_index']
class LibraryAsset(models.Model):
"""预设素材库(与 Project 无关,全局共享)
主题分类classical/modern/astronomy/minimal ...
角色J / Q / K / joker
"""
id = models.BigAutoField(primary_key=True)
theme_id = models.CharField(max_length=50, db_index=True) # classical / modern ...
theme_name = models.CharField(max_length=100) # 古典宫廷 / 现代人物 ...
role = models.CharField(max_length=10, db_index=True) # 'J' / 'Q' / 'K' / 'joker'
role_name = models.CharField(max_length=50) # '王子' / '皇后' ...
asset_id = models.CharField(max_length=50) # 'prince' / 'queen' ...
label = models.CharField(max_length=100) # '古典·王子'
description = models.CharField(max_length=255, blank=True)
file_path = models.CharField(max_length=255) # library/classical/prince.svg
file_name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.theme_name}/{self.role_name}'
class Meta:
ordering = ['theme_id', 'role', 'asset_id']
unique_together = [['theme_id', 'asset_id']]